【问题标题】:Automated test for a async Command in MVVMMVVM 中异步命令的自动化测试
【发布时间】:2017-08-25 09:03:51
【问题描述】:

我有一个异步 Command 类,如下所示:

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);

此测试有更好的解决方案吗?可能取决于实际需要多长时间,并且不估计等待多长时间。

【问题讨论】:

    标签: c# wpf unit-testing mvvm integration-testing


    【解决方案1】:

    你可以参考@StephenCleary 的一篇好博文: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();
      }
    }
    

    但您可以在链接的博客文章中找到更高级的变体。您可以在代码中一直使用IAsyncCommands,以便对其进行测试。您使用的 MVVM 框架也会很高兴,因为该接口基于 ICommand

    【讨论】:

      【解决方案2】:

      这个测试有更好的解决方案吗?也许取决于实际需要多长时间,并且不估计要等待多长时间。

      当然:使命令可等待并await 它。 async void 方法是不好的做法,用于事件处理程序。

      Mvvm.AsyncReactiveUI 中有可用的等待命令,您可以使用或至少查看一下以供参考。

      【讨论】:

      • 谢谢。看来您的链接将我指向 StephanCleary。这与@dymanoid 建议的相同。既然他提供了例子,我会给他接受的答案,但要投赞成票。
      【解决方案3】:

      如果您有要检查的状态,您可能应该只使用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);
      

      【讨论】:

      • 非常有用。下次我无法等待执行时,我可能会考虑到这一点。我已经被证明可以使等待成为可能。谢谢。
      猜你喜欢
      • 2015-08-24
      • 2015-02-26
      • 2013-01-27
      • 1970-01-01
      • 1970-01-01
      • 2015-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多