如何测试使用BackgroundWorker加载的ViewModel? [英] How to test a ViewModel that loads with a BackgroundWorker?

查看:50
本文介绍了如何测试使用BackgroundWorker加载的ViewModel?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

关于MVVM的优点之一是ViewModel的可测试性.在我的特定情况下,我有一个VM,该VM会在调用命令时加载一些数据,并进行相应的测试:

One of the nice things about MVVM is the testability of the ViewModel. In my particular case, I have a VM that loads some data when a command is called, and its corresponding test:

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

这就是所有非常简单的东西.但是,我真正想做的是使用BackgroundWorker加载数据,并在屏幕上显示正在加载"消息.所以我将虚拟机更改为:

So that is all pretty simple stuff. However, what I would really like to do is load the data using a BackgroundWorker, and have a 'loading' message displayed on screen. So I change the VM to:

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

这在运行时在视觉上可以正常工作,但是由于未立即加载该属性,我的测试现在失败了.谁能建议一种测试这种负载的好方法?我想我需要的是这样的东西:

This works fine visually at runtime, however my test now fails because of the property not being loaded immediately. Can anyone suggest a good way to test this kind of loading? I suppose what I need is something like:

[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.");
}

但是,这似乎很笨拙...可以,但是感觉很脏. 有更好的建议吗?

However that seems really clunky... It works, but just feels dirty. Any better suggestions?

最终解决方案:

基于David Hall的回答,为了模拟BackgroundWorker,我最终在BackgroundWorker周围做了一个非常简单的包装,该包装定义了两个类,一个类异步加载数据,一个同步加载.

Based on David Hall's answer, to mock a BackgroundWorker, I ended up doing this fairly simple wrapper around BackgroundWorker that defines two classes, one that loads data asynchronously, and one that loads synchronously.

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

因此,然后在我的Unity配置中,我可以使用SyncWorker进行测试,并使用AsyncWorker进行生产.然后,我的ViewModel变为:

So then in my Unity configuration, I can use SyncWorker for testing, and AsyncWorker for production. My ViewModel then becomes:

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

请注意,在我的测试中,我标记为wcfClient的东西实际上也是模拟的,因此在调用vm.LoadDataCommand.Execute()之后,我还可以验证是否调用了wcfClient.LoadData().

Note that the thing i have marked as wcfClient is actually a Mock in my tests too, so after the call to vm.LoadDataCommand.Execute() I can also validate that wcfClient.LoadData() was called.

推荐答案

引入一个模拟/伪造的后台工作程序,该程序可验证您是否正确调用了它,但会立即返回罐头响应.

Introduce a mock/fake background worker which verifies that you call it correctly but returns immediately with a canned response.

更改您的视图模型以允许通过属性注入或构造函数注入(我在下面显示构造函数注入)注入依赖项,然后在测试时通过伪造的背景工作者.在现实世界中,创建虚拟机时需要注入真实的实现.

Change your view model to allow for injection of dependencies, either through property injection or constructor injection (I'm showing constructor injection below) and then when testing you pass in the fake background worker. In the real world you inject the real implementation when creating the 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();    
    }

}

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

Depending on your framework (Unity/Prism in your case) wiring up the correct background worker should not be too hard to do.

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

The one problem with this approach is that most Microsoft classes including BackGroundWorker don't implement interfaces so faking/mocking them can be tricky.

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

The best approach I've found is to create your own interface for the object to mock and then a wrapper object that sits on top of the actual Microsoft class. Not ideal since you have a thin layer of untested code, but at least that means the untested surface of your app moves into testing frameworks and away from application code.

这篇关于如何测试使用BackgroundWorker加载的ViewModel?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆