随着.NET社区对Task Parallel Library (TPL)的逐渐熟悉,其带来的语言特性也变得越来越流行。其中,异常处理是一个显著的区别。在任务中运行的用户代码抛出的未处理异常会传播回加入的线程。为了将所有异常传播回调用线程,Task基础设施将它们包装在一个AggregateException实例中。这种处理方式清晰且直接,具体的详细信息可以在MSDN上找到。
当开始为异步方法编写单元测试时,发现不能再使用通常的技术了。为了断言在特定情况下某个单元抛出了特定的异常,总是使用ExpectedException属性。为了更清楚地说明这一点,将给出一个使用示例。
假设有以下类:
public bool MySimpleMethod(List param)
{
if (param == null)
{
throw new ArgumentNullException("param");
}
return true;
}
将编写一个测试,以断言在将null作为参数传递给此方法调用时,该方法应该引发ArgumentNullException。为此,请考虑以下代码:
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MySimpleMethod_Throws_ArgumentNullException()
{
sut.MySimpleMethod(null);
}
一旦执行,这个测试将通过。现在让编写上述方法的异步版本。
public async Task MySimpleMethodAsync(List param)
{
if (param == null)
{
throw new ArgumentNullException("param");
}
await Task.Delay(100);
return true;
}
由于知道异常被包装在AggregateException中,因此预期测试将失败。那么,应该如何测试这种情况呢?使用代码Wait,非常喜欢ExpectedException属性,希望有一个属性可以检查任何聚合的异常是否是期望的类型。
经过快速搜索,没有找到任何适合东西。那一刻,ExpectedAggregateExceptionAttribute诞生了。
遵循了ExpectedException的模式,这就是想到的。
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ExpectedAggregateExceptionAttribute : ExpectedExceptionBaseAttribute
{
// ...
}
简而言之,检查抛出的异常是否为AggregateException类型,如果是,再次检查任何内部异常是否为请求的类型。如果这个条件不匹配,将抛出一个新的异常,这将使单元测试失败。如果现在使用新属性而不是ExpectedExceptionAttribute,这个测试将成功。
[TestMethod]
[ExpectedAggregateException(typeof(ArgumentNullException))]
public void MySimpleMethodAsync_Throws_ArgumentNullException_2()
{
sut.MySimpleMethodAsync(null).Wait();
}
为了确保新属性正常工作,将进行一些其他测试。首先,期望如果抛出了异常,单元测试将失败:
[TestMethod]
[ExpectedAggregateException(typeof(ArgumentNullException))]
public void ArgumentNullException_Fail_If_No_Exception()
{
sut.MySimpleMethodAsync(new List()).Wait();
}
请注意,这个测试不应该通过(不要将其包含在解决方案中)。
同样,如果内部异常中没有预期的异常类型,测试应该失败:
[TestMethod]
[ExpectedAggregateException(typeof(ArgumentNullException))]
public void ArgumentNullException_Fail_If_Wrong_Inner_Exception()
{
Task.Factory.StartNew(() =>
{
throw new ArgumentOutOfRangeException();
});
}
由于两个测试都失败了,可以得出结论,这个新属性按预期工作。可以将其打包为想要的方式,在自己的公共库中,在新的或现有的自定义测试库中,或在任何项目中。