单元测试如何确认已经抛出了异常?

12

我正在为一个C#类编写单元测试。我的其中一个测试应该在添加数据时导致该方法抛出异常。如何使用我的单元测试来确认是否已经抛出了异常?

我该怎么用我的单元测试来验证是否抛出了异常呢?

11
哪种单元测试框架? - stuartd
Visual Studio自带的标准单元测试系统,我不确定它是否有特定的名称。 - Richard
5个回答

22

这取决于你使用的单元测试框架。在所有情况下,你都可以像下面这样做:

[Test]
public void MakeItGoBang()
{
     Foo foo = new Foo();
     try
     {
         foo.Bang();
         Assert.Fail("Expected exception");
     }
     catch (BangException)
     { 
         // Expected
     }
}
在一些框架中,测试方法可以添加一个属性来表达预期的异常,或者可能有一个方法,例如:
[Test]
public void MakeItGoBang()
{
     Foo foo = new Foo();
     Assert.Throws<BangException>(() => foo.Bang());
}

这样限制范围更好,如果您将其应用于整个测试,则即使错误行引发异常,测试也可能通过。


第一种方法并不好,因为它在抛出异常的代码路径中没有进行断言。一个好的测试总是需要断言某些期望发生了。我更喜欢在catch块中设置标志或将异常保存在本地变量中,在最后进行断言。 - Brian Geihsler
@Brian:这个断言隐含在我们没有到达Assert.Fail(所以已经抛出了一个异常),并且只捕获了正确的异常(因此不能抛出另一个异常)。我对这种风格一点也不反感-在我看来,它非常清晰地表明了期望的结果。但是我更喜欢Assert.Throws,因为它更加简洁。请注意,如果您需要检查异常的任何其他细节,可以在catch块中轻松完成。 - Jon Skeet
我同意Assert.Throws是理想的选择。虽然仍然很容易解释不到达assert.Fail()意味着测试通过,但我认为在答案中提供的方法和保存异常并在catch块后进行断言的方法之间选择,可以更清晰地表达测试的意图。最终,这是一个风格选择,并且可能更重要的是,呼吁Microsoft改进其断言功能以与其他框架竞争。 - Brian Geihsler
@Brian:这绝对是一种风格选择——我个人真的不喜欢“保留异常”方法,但我可以理解它对那些遵循AAA方法的人有吸引力。 - Jon Skeet
当然,即使您的框架不支持它,实现Assert.Throws<>也是微不足道的... - Ohad Schneider

13
[ExpectedException(typeof(System.Exception))]

用于 Visual Studio 单元测试框架。

参见 MSDN:

如果抛出了预期的异常,则测试方法将通过。

如果抛出的异常继承自预期异常,则测试将失败。


6
很不幸,这种方法的特点是只要在方法中抛出了异常(任何地方都可以),就会被执行,而不仅仅是你期望的位置。 - Jon Skeet

5
如果你想遵循三A模式(安排,执行,断言),不论测试框架如何,你可以选择以下方法:
[Test]
public void MyMethod_DodgyStuffDone_ThrowsRulesException() {

    // arrange
    var myObject = CreateObject();
    Exception caughtException = null;

    // act
    try {
        myObject.DoDodgyStuff();
    }
    catch (Exception ex) {
        caughtException = ex;
    }

    // assert
    Assert.That(caughtException, Is.Not.Null);
    Assert.That(caughtException, Is.TypeOf<RulesException>());
    Assert.That(caughtException.Message, Is.EqualTo("My Error Message"));
}

1
如果您正在使用Nunit,您可以为您的测试添加标签
[ExpectedException( "System.ArgumentException" ) )]

2
我不喜欢这个答案,因为它会使测试通过,即使在你不期望的地方抛出异常。 - Simone
1
@abatishchev 如果你在测试中调用了三个不同的方法,这个属性只是确保ArgumentException必须在某个地方被引发,但也许你想确保ArgumentException仅由这三个方法调用中的一个抛出。 - Simone
你说过你知道你的测试会抛出异常,而现在你想测试它,对吗? - MBen
1
不,通常您希望测试一个特定操作是否引发异常。如果其他操作引发异常,则测试不应通过。我也不喜欢以字符串形式指定异常类型,而不是使用typeof(...)的方式。 - Jon Skeet

0
你可以使用 Verify() 方法进行单元测试,并比较返回类型中的异常消息。
InterfaceMockObject.Verify(i =>i.Method(It.IsAny<>())), "Your received Exception Message");

你需要在 It.IsAny<> 块中写入一些类名或数据类型


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接