单元测试:检测异常是否已被捕获

4
我有一个需要放置try/catch块的方法。这是因为该方法被从Task调用。如果抛出异常,线程将会结束。我可以使用Task.ContinueWith来处理错误并在发生错误时启动新任务,但使用try/catch就可以达到效果。
无论如何,我知道我需要放置try/catch块,但是如何测试是否捕获了异常呢?
一种方法是触发现有的事件,该事件接受一个字符串参数,然后在单元测试中,测试此字符串是否与我期望的字符串匹配。不确定这是否好,但想知道处理此场景的最佳方法是什么。

不要捕获异常,或者抛出异常。 - Zenwalker
如果您的Catch块未处理异常,则单元测试将无论如何失败,抛出特定异常,但如果它被捕获/处理,则测试将通过。不确定是否有任何特定的解决方法。 - Rahul
@zenwalker 好的,假设我确实为所有可能的例外情况设置了检查。但我仍然想要测试异常是否被正确处理? - Jon
你真的需要在这个地方捕获这个异常吗? 记住:在单元测试中,你应该分别测试每个方法。 如果你正在测试使用另一个方法的东西,你可以模拟这个最后一个方法。 在测试中,你可以使用ExpectedException属性来捕获异常。 - Silvio Delgado
显示剩余6条评论
4个回答

3

如果你正在进行TDD测试驱动开发,你应该采取小步前进的方式,并关注你的类应该做什么。以下是一个例子。你想要打印一些报告。首先(我假设你是从外向内移动),你设计了一个名为Report的类,它应该通过Printer来打印自己(这只是一个例子)。所以你写了一个测试:

[Test]
public void ShouldPrintItself()
{
   Mock<IPrinter> printer = new Mock<IPrinter>();            
   Report report = new Report(printer.Object);
   report.Text = "foo";

   report.Print();
   printer.Verify(p => p.Print("foo"));
}

你需要为report.Print方法编写一些实现。同时,你正在设计IPrinter接口。接下来,你了解到打印机有时可能会出现异常(例如缺纸)。顺便说一下,这是你的情况。因此,你将report.Print方法重命名为类似TryPrint的名称,更改第一个测试并创建新的测试:

[Test]
public void ShouldPrint()
{
    Mock<IPrinter> printer = new Mock<IPrinter>();            
    Report report = new Report(printer.Object);
    report.Text = "foo";

    Assert.True(report.TryPrint());
    printer.Verify(p => p.Print("foo"));
}

[Test]
public void ShouldNotPrint()
{
    Mock<IPrinter> printer = new Mock<IPrinter>();
    printer.Setup(p => p.Print(It.IsAny<string>())).Throws<Exception>();
    Report report = new Report(printer.Object);
    report.Text = "foo";

    Assert.False(report.TryPrint());
}

然后你回到TryPrint方法。现在在调用打印机的代码周围添加try catch块,并使测试通过(在应用程序中也应该这样做):

public bool TryPrint()
{
    try
    {
        _printer.Print(_text);
        return true;
    }
    catch (Exception ex)
    {
        // of course, log exception
        return false;
    }           
}

当你完成这里的操作后,可以开始创建打印机。在你的情况下,这将是组件测试。好消息是 - 你已经设计了IPrinter接口。因此,你可以编写测试并验证在某些情况下是否会抛出异常:

[Test]
public void ShouldThrowExceptionWhenNoPaperLeft()
{
    Printer printer = new Printer();
    printer.PagesCount = 0;

    Exception ex = Assert.Throws<Exception>(() => printer.Print("foo"));
    Assert.That(ex.Message, Is.EqualTo("Out of paper"));
}

当然,您需要编写组件实现以通过此测试。之后,您的带有 try catch 块的类按预期工作,并且您的组件按预期工作,在应该引发异常时引发异常。


1

当你添加try/catch时,意味着你希望在被测试的类调用另一个抛出异常的类时有期望的行为。try/catch是“如何”,测试行为。

因此,使用mocking,在调用“其他”服务时抛出异常,并测试你的类是否达到了预期的效果。首先编写测试,这将让你思考为什么要添加try/catch。


我先进行测试。我知道我需要在其中加入try/catch,但不确定如何确认它已被捕获。我可以不加一个try/catch,然后在创建任务的其他类周围加上一个try/catch,但我认为我仍然处于同样的位置。 - Jon

1

你应该测试你的应用程序实际执行的是什么,而不是如何执行。因此,只需模拟您的任务抛出异常,并验证在这种情况下是否引发了新任务。


0

这是我选择的,但我对人们的意见很感兴趣:

[Test]
public void DoSomething_NullParameterEntered_ShouldCatchException()
{
    var component = new Whatever();

    try
    {
        component.DoSomething(null);  //If a try/catch block exists it will not fall into the below catch
    }
    catch
    {
        Assert.Fail();
    }
}

如果try/catch块不存在,你的测试将会失败,因为异常已经被抛出。所以,你可以直接为DoSomething(null)编写没有try/catch的测试。并且你可以编写期望从你的组件中抛出异常的测试,用于测试DoSomething(not null)。请参考下面的示例。 - Sergey Berezovskiy

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