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 可观察对象进行单元测试

评论

此博客中的热门博文

in 参数(C# 7.2)

C# ref 局部变量和 ref return

类 ref 结构体(C# 7.2)