MVVM 中异步命令的自动化测试
Automated test for a async Command in MVVM
我有一个异步 Command
class,像这样:
public AsyncDelegateCommand(Func<Task> execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public virtual bool CanExecute(object parameter)
{
if(executing)
return false;
if(canExecute == null)
return true;
return canExecute();
}
public async void Execute(object parameter) // Notice "async void"
{
executing = true;
CommandManager.InvalidateRequerySuggested();
if(parameter != null && executeWithParameter != null)
await executeWithParameter(parameter);
else if(execute != null)
await execute();
executing = false;
CommandManager.InvalidateRequerySuggested();
}
它的名字是这样的:
FindProductCommand = new AsyncDelegateCommand(TryToFindProduct, () => CanFindProduct() && connector.HasConnection);
private async Task TryToFindProduct()
{
//code
}
当我进行单元测试时,它工作得很好,因为我从任务中立即返回。
然而,在编写我的集成测试时,我 运行 遇到了麻烦。我无法等待 Execute
,因为它是 void
,而且我无法将其更改为 Task
。我最终这样做了:/
findProductViewModel.FindProductCommand.Execute(null);
Thread.Sleep(2000);
var informationViewModel = findProductViewModel.ProductViewModel.ProductInformationViewModel;
Assert.AreEqual("AFG00", informationViewModel.ProductGroup);
这个测试有没有更好的解决方案?可能是一些依赖实际需要多长时间的东西,并没有估计要等待多长时间。
如果你有一个状态要检查,你应该只使用 SpinWait.SpinUntil:
它会比 Thread.Sleep
可靠得多,因为它允许您在继续之前检查条件是否为真。
例如
findProductViewModel.FindProductCommand.Execute(null);
SpinWait.SpinUntil(() => findProductViewModel.ProductViewModel.ProductInformationViewModel != null, 5000);
var informationViewModel = findProductViewModel.ProductViewModel.ProductInformationViewModel;
Assert.AreEqual("AFG00", informationViewModel.ProductGroup);
您可以参考@StephenCleary 的一篇不错的博客post:https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
通常要避免 async void
,因此他为异步命令引入了一个新接口(及其基本实现):IAsyncCommand
。此接口包含您可以在测试中等待的方法 async Task ExecuteAsync(object parameter)
。
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
public abstract class AsyncCommandBase : IAsyncCommand
{
public abstract bool CanExecute(object parameter);
public abstract Task ExecuteAsync(object parameter);
public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
这种异步命令的最简单实现如下所示:
public class AsyncCommand : AsyncCommandBase
{
private readonly Func<Task> _command;
public AsyncCommand(Func<Task> command)
{
_command = command;
}
public override bool CanExecute(object parameter)
{
return true;
}
public override Task ExecuteAsync(object parameter)
{
return _command();
}
}
但是您可以在链接的博客中找到更高级的变体 post。你可以在你的代码中一路使用IAsyncCommand
s,这样你就可以测试它们了。您使用的 MVVM 框架也会很高兴,因为该接口基于 ICommand
.
Is there a better solution for this test? Maybe something that is reliant on how long it actually takes, and doesn't estimate how long to wait.
当然:使命令可等待并 await
它。 async
void
方法是不好的做法,仅 用于事件处理程序。
Mvvm.Async and ReactiveUI 中有可用的可等待命令,您可以使用或至少看一看以供参考。
我有一个异步 Command
class,像这样:
public AsyncDelegateCommand(Func<Task> execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public virtual bool CanExecute(object parameter)
{
if(executing)
return false;
if(canExecute == null)
return true;
return canExecute();
}
public async void Execute(object parameter) // Notice "async void"
{
executing = true;
CommandManager.InvalidateRequerySuggested();
if(parameter != null && executeWithParameter != null)
await executeWithParameter(parameter);
else if(execute != null)
await execute();
executing = false;
CommandManager.InvalidateRequerySuggested();
}
它的名字是这样的:
FindProductCommand = new AsyncDelegateCommand(TryToFindProduct, () => CanFindProduct() && connector.HasConnection);
private async Task TryToFindProduct()
{
//code
}
当我进行单元测试时,它工作得很好,因为我从任务中立即返回。
然而,在编写我的集成测试时,我 运行 遇到了麻烦。我无法等待 Execute
,因为它是 void
,而且我无法将其更改为 Task
。我最终这样做了:/
findProductViewModel.FindProductCommand.Execute(null);
Thread.Sleep(2000);
var informationViewModel = findProductViewModel.ProductViewModel.ProductInformationViewModel;
Assert.AreEqual("AFG00", informationViewModel.ProductGroup);
这个测试有没有更好的解决方案?可能是一些依赖实际需要多长时间的东西,并没有估计要等待多长时间。
如果你有一个状态要检查,你应该只使用 SpinWait.SpinUntil:
它会比 Thread.Sleep
可靠得多,因为它允许您在继续之前检查条件是否为真。
例如
findProductViewModel.FindProductCommand.Execute(null);
SpinWait.SpinUntil(() => findProductViewModel.ProductViewModel.ProductInformationViewModel != null, 5000);
var informationViewModel = findProductViewModel.ProductViewModel.ProductInformationViewModel;
Assert.AreEqual("AFG00", informationViewModel.ProductGroup);
您可以参考@StephenCleary 的一篇不错的博客post:https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
通常要避免 async void
,因此他为异步命令引入了一个新接口(及其基本实现):IAsyncCommand
。此接口包含您可以在测试中等待的方法 async Task ExecuteAsync(object parameter)
。
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
public abstract class AsyncCommandBase : IAsyncCommand
{
public abstract bool CanExecute(object parameter);
public abstract Task ExecuteAsync(object parameter);
public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
这种异步命令的最简单实现如下所示:
public class AsyncCommand : AsyncCommandBase
{
private readonly Func<Task> _command;
public AsyncCommand(Func<Task> command)
{
_command = command;
}
public override bool CanExecute(object parameter)
{
return true;
}
public override Task ExecuteAsync(object parameter)
{
return _command();
}
}
但是您可以在链接的博客中找到更高级的变体 post。你可以在你的代码中一路使用IAsyncCommand
s,这样你就可以测试它们了。您使用的 MVVM 框架也会很高兴,因为该接口基于 ICommand
.
Is there a better solution for this test? Maybe something that is reliant on how long it actually takes, and doesn't estimate how long to wait.
当然:使命令可等待并 await
它。 async
void
方法是不好的做法,仅 用于事件处理程序。
Mvvm.Async and ReactiveUI 中有可用的可等待命令,您可以使用或至少看一看以供参考。