我不建议使用 ExpectedException 属性(因为它太受约束且容易出错)或在每个测试中编写 try/catch 块(因为它太复杂且容易出错)。使用设计良好的断言方法——由您的测试框架提供或自己编写。这是我编写和使用的内容。
public static class ExceptionAssert
{
private static T GetException<T>(Action action, string message="") where T : Exception
{
try
{
action();
}
catch (T exception)
{
return exception;
}
throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated. " + message);
}
public static void Propagates<T>(Action action) where T : Exception
{
Propagates<T>(action, "");
}
public static void Propagates<T>(Action action, string message) where T : Exception
{
GetException<T>(action, message);
}
public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
{
Propagates(action, validation, "");
}
public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
{
validation(GetException<T>(action, message));
}
}
使用示例:
[TestMethod]
public void Run_PropagatesWin32Exception_ForInvalidExeFile()
{
(test setup that might propagate Win32Exception)
ExceptionAssert.Propagates<Win32Exception>(
() => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
(more asserts or something)
}
[TestMethod]
public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
{
(test setup that might propagate FileNotFoundException)
ExceptionAssert.Propagates<FileNotFoundException>(
() => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
e => StringAssert.Contains(e.Message, "NotThere.exe"));
(more asserts or something)
}
注意事项
返回异常而不支持验证回调是一个合理的想法,但这样做会使此断言的调用语法与我使用的其他断言非常不同。
与其他人不同,我使用“传播”而不是“抛出”,因为我们只能测试异常是否从调用传播。我们无法直接测试是否引发了异常。但我想你可以想象 throws 的意思是:被抛出而不是被抓住。
最后的想法
在切换到这种方法之前,我考虑在测试仅验证异常类型时使用 ExpectedException 属性,并在需要更多验证时使用 try/catch 块。但是,我不仅要考虑每次测试使用哪种技术,而且随着需求的变化将代码从一种技术更改为另一种技术也不是一件容易的事。使用一种一致的方法可以节省脑力劳动。
因此,总而言之,这种方法具有以下特点:易用性、灵活性和稳健性(很难做错)。