异步 CTP, 如何对 ViewModel 的异步方法进行单元测试

3

我有一个单元测试(使用MSTest),如下所示:

[TestMethod]
public void MyTest()
{
    var viewModel = new MyViewModel();
    viewModel.Run();
    //Assert something here
}

Run 是一个返回 void 的异步方法。

假设 Run 的实现方式如下:

public async void Run()
{
    //Show a busy indicator here

    try
    {
        var result = await myAsyncModelClass.LongRunningOperation();

        //Use the results here
    }
    finally
    {
        //Hide the busy indicator here
    }
}

myAsyncModelClass.LongRunningOperation() 是一个异步方法,它返回一些 Task<T>,其中 T 是我的 ViewModel 感兴趣的结果。

我的问题是,我的测试以异步方式运行 Run 方法,因此我的断言在 Run 方法完成之前就被调用了。这很奇怪,因为当我设置断点时,finally 块永远不会被执行,因为断言失败了。如何使 Run 方法同步以便对其进行单元测试?

我也有一个对 myAsyncModelClass.LongRunningOperation() 的单元测试,但我只是调用了 Task<T>.Wait(),因为它返回一个任务。这使得在单元测试时它变成了同步的。

另外,我想提到的是,Run() 被一个 MVVM 框架神奇地通过 ICommand 调用。 void 可能需要作为返回类型,我将尝试一下。


嗨 - 我没有使用C#-async的经验(它破坏了我的VS安装),但如果它是一个异步函数,你可以调用Run吗?F#有一些类似的结构,但如果您尝试调用异步对象,则会出现编译错误。 - 无论如何,为什么不将其包装到任务中并等待它(或在测试中等待您的等待)? - Random Dev
我会返回一个任务,但这个方法也通过绑定到ICommand由MVVM框架调用。如果我进行更改,我不认为我的代码将在应用程序中执行。 - jonathanpeppers
2个回答

15

异步方法需要一个“返回到”的上下文。由于MSTest在线程池上运行,因此默认情况下异步方法也会在线程池线程上继续执行(而不会阻塞MSTest方法)。

在您的Async CTP安装目录下的(C# Testing) Unit Testing示例中,有一种类型称为GeneralThreadAffineContext,可以这样使用:

[TestMethod]
public void MyTest()
{
  MyViewModel viewModel = null;
  GeneralThreadAffineContext.Run(() =>
  {
    viewModel = new MyViewModel();
    viewModel.Run();
  });
  //Assert something here
}

还有特定的WPF和WinForms上下文,但线程相关的上下文应该适用于一般的ViewModel(不使用Dispatcher)。

更新2012-02-05:如果您可以更改ViewModel方法以返回Task,则还有另一种选择:新的AsyncUnitTests库。 安装该NuGet包,将TestClass更改为AsyncTestClass,然后您的异步单元测试可以更自然地编写:

[TestMethod]
public async void MyTest()
{
  MyViewModel viewModel = new MyViewModel();
  await viewModel.Run();
  //Assert something here
}

更新于2012-09-04: Visual Studio 2012 包含 async 单元测试,因此您不再需要使用 AsyncUnitTests 库:

[TestMethod]
public async Task MyTest()
{
  MyViewModel viewModel = new MyViewModel();
  await viewModel.Run();
  //Assert something here
}

那很好用。不过,我想我会构建自己的 GeneralThreadAffineContext 实现。 - jonathanpeppers
如果您感兴趣,我已经将我的代码作为 Nito.AsyncEx 库的一部分(称为“AsyncContext”)发布在了 http://nitoasyncex.codeplex.com/wikipage?title=AsyncContext 上。 - Stephen Cleary
如果有一种方法可以全局地将其挂钩到每个单元测试上,那就更好了。比如说,如果你只需要在单元测试中放置async关键字,然后MSTest就会处理它。也许在完整的C# 5中,我们会得到类似的东西。 - jonathanpeppers

3

自从Visual Studio 2012开始,MSTest支持异步测试方法。只需记住它们应该返回Task而不是void:

[TestMethod]
public async Task MyTest()
{
  MyViewModel viewModel = new MyViewModel();
  await viewModel.Run();
  //Assert something here
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接