运行 多个异步单元测试方法会产生错误,但 运行 它们各自不会
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);
}
VerifyCommand
和 GenerateCommand
实际上是实现 ICommand
的 AsyncRelayCommand
类型,因此它们是 async void
而我必须 运行使用 Task.Run()
在测试中将它们放在单独的线程上。 Method1
和Method2
实际上分别代表按钮使用的ExecuteMethod1CommandAsync
和ExecuteMethod2CommandAsync
命令。
问题是,如果我 运行 这些测试分开进行,它们都会通过。 但是,一旦我尝试同时测试它们,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
版本。 (如果你能接受这些包装器将不会被单元测试)。
在我的 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);
}
VerifyCommand
和 GenerateCommand
实际上是实现 ICommand
的 AsyncRelayCommand
类型,因此它们是 async void
而我必须 运行使用 Task.Run()
在测试中将它们放在单独的线程上。 Method1
和Method2
实际上分别代表按钮使用的ExecuteMethod1CommandAsync
和ExecuteMethod2CommandAsync
命令。
问题是,如果我 运行 这些测试分开进行,它们都会通过。 但是,一旦我尝试同时测试它们,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
版本。 (如果你能接受这些包装器将不会被单元测试)。