运行 多个异步单元测试方法会产生错误,但 运行 它们各自不会

Running multiple async unit test methods produces an error, but running them individually does not

在我的 WPF MVVM 中,我有两个 在单独 运行 时工作 单元测试,它们测试两个按钮命令方法:

[TestMethod]
public async Task TestMethod1()
{
    // Arrange
    var interfaceStub = new StubInterface();
    interfaceStub.Method = () => "Message";
    var viewModel = new ViewModel(interfaceStub);

    // Act
    await Task.Run(() => viewModel.GenerateCommand.Execute());

    // Assert
    Assert.AreEqual("Message", viewModel.Response);
}

[TestMethod]
public async Task TestMethod2()
{
    // Arrange
    var interfaceStub = new StubInterface();
    interfaceStub.Method = () => "Message";
    var viewModel = new ViewModel(interfaceStub);

    // Act
    await Task.Run(() => viewModel.VerifyCommand.Execute());

    // Assert
    Assert.AreEqual("Message", viewModel.Response);
}

VerifyCommandGenerateCommand 实际上是实现 ICommandAsyncRelayCommand 类型,因此它们是 async void 而我必须 运行使用 Task.Run() 在测试中将它们放在单独的线程上。 Method1Method2实际上分别代表按钮使用的ExecuteMethod1CommandAsyncExecuteMethod2CommandAsync命令。

问题是,如果我 运行 这些测试分开进行,它们都会通过。 但是,一旦我尝试同时测试它们Assert.AreEqual() 失败:

Expected: <"Message"> , Actual <>

这显然是因为其中一项测试没有等待线程完成,并且在断言步骤中尚未返回值。我将 System.Threading.Thread.Sleep(5000); 添加到两个测试方法中,然后当我再次将它们 运行 在一起时,两个测试都通过了(这进一步证明了这一点)。

为什么 运行单独设置它们会使测试方法等待,而 运行单独设置它们会使其中之一不等待?

编辑:当我 运行 它们分开时,所花费的时间:两者都约为 140 毫秒。当我 运行 将它们放在一起时:通过的那个又是 ~140 毫秒,另一个没有通过的只有 90 毫秒(证明我的发现)

编辑 2:执行方法:

// Executes the action
public async void Execute(object parameter)
{
    // Do something

     try
     {
         await execute(parameter);
     }
     finally
     {
         // Do something
     }
}

execute 是一个 Func<object, Task>。这显然是问题发生的地方——当 execute(parameter) 启动时,它立即 returns 返回到 TestMethod 中的任务。解决此问题的最佳方法是什么?

async void 意味着调用者无法确定 activity 何时完成。将其包装在 Task.Run() 中不会改变这一点。你现在有一个 Task 开始了一些事情 运行 然后它的工作完成了所以它被标记为完成尽管事实上它调用的 async void 方法可能还没有完成。

如果不对代码进行某种重新设计,您将无法解决此问题。如果您受到外部因素(interface/delegate 签名)的限制而不得不将这些方法设置为 void,请考虑在另一侧是否期望这些方法在 return没做完。如果是这种情况,那么不幸的是,最好的方法是撤消您在那里所做的 async 事情。

如果没有外在因素,将方法async Task改为直接await即可(不需要Task.Run)。

如果有外部因素,但您认为合同允许您 return 在工作未完成的情况下,那么我建议将您拥有的方法数量加倍。通过 async Task 制作一组并对这些进行单元测试。然后让 async void 方法成为 thinnest 可能的包装器,它只调用 async Task 版本。 (如果你能接受这些包装器将不会被单元测试)。