C# 对 async 方法进行单元测试
一、问题
假设需要对某个 async
方法进行单元测试。
二、解决方案
大多数现代单元测试框架支持 async Task
单元测试方法,包括 MSTest、NUnit、xUnit。从 Visual Studio 2012 起,MSTest 开始支持这些测试,如果使用别的单元测试框架,那么需要升级到最新版本。
以下示例展示了 async
MSTest 单元测试:
[TestMethod]
public async Task MyMethodAsync_ReturnsFalse()
{
var objectUnderTest = ...;
bool result = await objectUnderTest.MyMethodAsync();
Assert.IsFalse(result);
}
单元测试框架会留意到方法的返回类型是 Task
,然后自动等待任务完成,并将测试标记为“成功”或“失败”。
如果所采用的单元测试框架不支持 async Task
单元测试,则需要借助一些辅助操作,等待被测试的异步操作。有一种选项是使用 GetAwaiter().GetResult()
来同步阻塞任务,如果使用这种办法来替代 Wait()
,那么任务即使有异常也不会被包装进 AggregateException
。然而,我个人更倾向于使用 Nito.AsyncEx NuGet
包中的 AsyncContext
类型:
[TestMethod]
public void MyMethodAsync_ReturnsFalse()
{
AsyncContext.Run(async () =>
{
var objectUnderTest = ...;
bool result = await objectUnderTest.MyMethodAsync();
Assert.IsFalse(result);
});
}
AsyncContext.Run
会等待,直至所有异步方法完成。
三、讨论
起初,模拟异步依赖可能会有些笨拙。不过,至少要测试一下方法如何响应同步成功(通过 Task.FromResult
模拟)、同步错误(通过 Task.FromException
模拟)和异步成功(通过 Task.Yield
和返回值模拟)。关于 Task.FromResult
和 Task.FromException
,参见 2.2 节。Task.Yield
可以用来强制异步行为,主要用于单元测试:
interface IMyInterface
{
Task<int> SomethingAsync();
}
class SynchronousSuccess : IMyInterface
{
public Task<int> SomethingAsync()
{
return Task.FromResult(13);
}
}
class SynchronousError : IMyInterface
{
public Task<int> SomethingAsync()
{
return Task.FromException<int>(new InvalidOperationException());
}
}
class AsynchronousSuccess : IMyInterface
{
public async Task<int> SomethingAsync()
{
await Task.Yield(); // 强制异步行为
return 13;
}
}
在测试异步代码时,死锁和竞争条件可能比测试同步代码时出现得更频繁。针对每个测试单独设置超时很有用,在 Visual Studio 里,可以通过对解决方案添加测试设置文件来设置独立的测试超时。这个设置的默认值相当高,我通常设置为 2 秒。
可以从
Nito.AsyncEx
NuGet 包中获取AsyncContext
类型。
(完)
相关阅读:
C# 对 async 方法进行单元测试
C# 对预期失败的 async 方法进行单元测试
C# 对 async void 方法进行单元测试
C# 对数据流网格进行单元测试
C# 对 System.Reactive 可观察对象进行单元测试
C# 通过伪造调度对 System.Reactive 可观察对象进行单元测试
评论
发表评论