如何测试使用BackgroundWorker加载的ViewModel?

Cod*_*ike 13 c# wpf unit-testing mvvm backgroundworker

MVVM的一个好处是ViewModel的可测试性.在我的特定情况下,我有一个VM,它在调用命令时加载一些数据,以及相应的测试:

public class MyViewModel
{
    public DelegateCommand LoadDataCommand { get; set; }

    private List<Data> myData;
    public List<Data> MyData
    {
        get { return myData; }
        set { myData = value; RaisePropertyChanged(() => MyData); }
    }

    public MyViewModel()
    {
        LoadDataCommand = new DelegateCommand(OnLoadData);
    }

    private void OnLoadData()
    {
        // loads data over wcf or db or whatever. doesn't matter from where...
        MyData = wcfClient.LoadData();
    }
}

[TestMethod]
public void LoadDataTest()
{
    var vm = new MyViewModel();
    vm.LoadDataCommand.Execute();
    Assert.IsNotNull(vm.MyData);
}
Run Code Online (Sandbox Code Playgroud)

所以这一切都很简单.但是,我真正想做的是使用a加载数据BackgroundWorker,并在屏幕上显示"加载"消息.所以我将VM更改为:

private void OnLoadData()
{
    IsBusy = true; // view is bound to IsBusy to show 'loading' message.

    var bg = new BackgroundWorker();
    bg.DoWork += (sender, e) =>
    {
      MyData = wcfClient.LoadData();
    };
    bg.RunWorkerCompleted += (sender, e) =>
    {
      IsBusy = false;
    };
    bg.RunWorkerAsync();
}
Run Code Online (Sandbox Code Playgroud)

这在运行时可视性很好,但我的测试现在因为没有立即加载属性而失败.有人能建议一种测试这种装载的好方法吗?我想我需要的是:

[TestMethod]
public void LoadDataTest()
{
    var vm = new MyViewModel();
    vm.LoadDataCommand.Execute();

    // wait a while and see if the data gets loaded.
    for(int i = 0; i < 10; i++)
    {
        Thread.Sleep(100);
        if(vm.MyData != null)
            return; // success
    }
    Assert.Fail("Data not loaded in a reasonable time.");
}
Run Code Online (Sandbox Code Playgroud)

然而,这看起来真的很笨......它有效,但只是感觉很脏.有更好的建议吗?


最终解决方案:

根据David Hall的回答,为了模拟一个BackgroundWorker,我最终做了这个相当简单的包装,BackgroundWorker它定义了两个类,一个是异步加载数据,另一个是同步加载的.

  public interface IWorker
  {
    void Run(DoWorkEventHandler doWork);
    void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete);
  }

  public class AsyncWorker : IWorker
  {
    public void Run(DoWorkEventHandler doWork)
    {
      Run(doWork, null);
    }

    public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
    {
      var bg = new BackgroundWorker();
      bg.DoWork += doWork;
      if(onComplete != null)
        bg.RunWorkerCompleted += onComplete;
      bg.RunWorkerAsync();
    }
  }

  public class SyncWorker : IWorker
  {
    public void Run(DoWorkEventHandler doWork)
    {
      Run(doWork, null);
    }

    public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
    {
      Exception error = null;
      var args = new DoWorkEventArgs(null);
      try
      {
        doWork(this, args);
      }
      catch (Exception ex)
      {
        error = ex;
        throw;
      }
      finally
      {
        onComplete(this, new RunWorkerCompletedEventArgs(args.Result, error, args.Cancel));
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

那么在我的Unity配置中,我可以使用SyncWorker进行测试,使用AsyncWorker进行生产.我的ViewModel然后变成:

public class MyViewModel(IWorker bgWorker)
{
    public void OnLoadData()
    {
        IsBusy = true;
        bgWorker.Run(
          (sender, e) =>
          {
            MyData = wcfClient.LoadData();
          },
          (sender, e) =>
          {
            IsBusy = false;
          });
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我标记的wcfClient内容实际上也是我的测试中的Mock,因此在调用vm.LoadDataCommand.Execute()I之后也可以验证是否wcfClient.LoadData()已调用.

Dav*_*all 13

介绍一个模拟/伪造的后台工作程序,它验证您是否正确调用它,但立即返回一个预设响应.

更改您的视图模型以允许注入依赖项,通过属性注入或构造函数注入(我在下面显示构造函数注入),然后在测试时传入伪后台工作程序.在现实世界中,您在创建VM时注入了真实的实现.

public class MyViewModel
{
    private IBackgroundWorker _bgworker;

    public MyViewModel(IBackgroundWorker bgworker)
    {
        _bgworker = bgworker;
    }

    private void OnLoadData()    
    {        
        IsBusy = true; // view is bound to IsBusy to show 'loading' message.        

        _bgworker.DoWork += (sender, e) =>        
        {          
            MyData = wcfClient.LoadData();        
        };        
        _bgworker.RunWorkerCompleted += (sender, e) =>        
        {          
            IsBusy = false;        
        };        
        _bgworker.RunWorkerAsync();    
    }

}
Run Code Online (Sandbox Code Playgroud)

根据您的框架(在您的情况下为Unity/Prism)连接正确的后台工作人员不应该太难.

这种方法的一个问题是大多数Microsoft类(包括BackGroundWorker)都没有实现接口,因此伪造/模拟它们可能会很棘手.

我发现的最好的方法是为要模拟的对象创建自己的接口,然后创建一个位于实际Microsoft类之上的包装器对象.不太理想,因为你有一层薄薄的未经测试的代码,但至少这意味着你的应用程序未经测试的表面会进入测试框架并远离应用程序代码.