C# 对预期失败的 async 方法进行单元测试
一、问题
假设你需要编写单元测试来检查 async Task
方法的某次失败。
二、解决方案
如果在进行桌面端开发或服务器端开发,那么 MSTest 能够通过常规的 ExpectedException
属性来支持失败测试:
// 此解决方案并非推荐方案,稍后解释原因
[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public async Task Divide_WhenDenominatorIsZero_ThrowsDivideByZero()
{
await MyClass.DivideAsync(4, 0);
}
然而,此解决方案并非最佳,ExpectedException
其实是个糟糕的设计,单元测试调用的所有方法都可能抛出它期待的异常。与其整体检查单元测试,不如检查特定部分的代码是否抛出了异常。
大多数现代单元测试框架以某种形式包含了 Assert.ThrowsAsync<TException>
。比如,可以像下面这样使用 xUnit 的 ThrowsAsync
。
[Fact]
public async Task Divide_WhenDenominatorIsZero_ThrowsDivideByZero()
{
await Assert.ThrowsAsync<DivideByZeroException>(async () =>
{
await MyClass.DivideAsync(4, 0);
});
}
别忘了等待由
ThrowsAsync
返回的任务!await
会传递所有检测到的断言失败。如果忘了使用await
,并无视了编译器警告,则不论方法行为如何,单元测试都会标记成功。
但是,另一些单元测试框架并不包含与 async
兼容的 ThrowsAsync
。如果遇到这种情况,那么可以选择自行创建:
/// <summary>
/// 确保异步委托抛出异常。
/// </summary>
/// <typeparam name="TException">
/// 预期的异常类型。
/// </typeparam>
/// <param name="action">所需测试的异步委托。</param>
/// <param name="allowDerivedTypes">
/// 是否应当接受派生类型。
/// </param>
public static async Task<TException> ThrowsAsync<TException>(Func<Task> action,
bool allowDerivedTypes = true)
where TException : Exception
{
try
{
await action();
var name = typeof(Exception).Name;
Assert.Fail($"Delegate did not throw expected exception {name}.");
return null;
}
catch (Exception ex)
{
if (allowDerivedTypes && !(ex is TException))
Assert.Fail($"Delegate threw exception of type {ex.GetType().Name}" +
$", but {typeof(TException).Name} or a derived type was expected.");
if (!allowDerivedTypes && ex.GetType() != typeof(TException))
Assert.Fail($"Delegate threw exception of type {ex.GetType().Name}" +
$", but {typeof(TException).Name} was expected.");
return (TException)ex;
}
}
可以把这个方法当作其他任意 Assert.ThrowsAsync<TException>
方法来使用。别忘了用 await
等待返回值!
三、讨论
测试错误处理与测试成功场景一样重要,甚至说更为重要也不为过。这是因为,成功场景意味着在软件发布之前,每个人都已经尝试过。当然,如果应用程序行为怪异,那么就存在意料之外的错误情况。
然而,本书建议开发人员摈弃 ExpectedException
。比起在测试中随时测试异常,测试从特定节点抛出的异常更为可行。与其使用 ExpectedException
,不如使用 ThrowsAsync
或者 ThrowsAsync
实现,就像上例中的那样。
(完)
相关阅读:
C# 对 async 方法进行单元测试
C# 对预期失败的 async 方法进行单元测试
C# 对 async void 方法进行单元测试
C# 对数据流网格进行单元测试
C# 对 System.Reactive 可观察对象进行单元测试
C# 通过伪造调度对 System.Reactive 可观察对象进行单元测试
评论
发表评论