如何测试是否不引发异常?单元测试的背景处理测试的有效和错误输入TL;DR

2022-08-31 04:45:29

我知道这样做的一种方法是:

@Test
public void foo() {
   try {
      // execute code that you expect not to throw Exceptions.
   } catch(Exception e) {
      fail("Should not have thrown any exception");
   }
}

有没有更清洁的方法可以做到这一点?(可能使用朱尼特的?@Rule


答案 1

你以错误的方式接近这一点。只需测试您的功能:如果引发异常,测试将自动失败。如果未引发异常,则测试将全部变为绿色。

我注意到这个问题不时引起人们的兴趣,所以我会扩展一下。

单元测试的背景

在进行单元测试时,重要的是要为自己定义您认为的工作单元。基本上:对代码库的提取,其中可能包括也可能不包含表示单个功能的多个方法或类。

或者,正如 Roy Osherove 的第 2 版《单元测试的艺术》第 11 页所定义的那样:

单元测试是一段自动化的代码,它调用正在测试的工作单元,然后检查有关该单元的单个最终结果的一些假设。单元测试几乎总是使用单元测试框架编写。它可以轻松编写并快速运行。它值得信赖,可读且可维护。只要生产代码没有改变,它的结果就是一致的。

重要的是要认识到,一个工作单元通常不仅仅是一种方法,但在最基本的层面上,它是一种方法,之后它被其他工作单元封装。

enter image description here

理想情况下,您应该为每个单独的工作单元提供一个测试方法,以便您始终可以立即查看出现问题的地方。在这个例子中,有一个基本方法,它将返回一个用户,总共有3个单位的作品。getUserById()

第一个工作单元应测试在输入有效和无效的情况下是否返回有效用户。
数据源引发的任何异常都必须在此处处理:如果不存在用户,则应进行一个测试,以证明在找不到用户时会引发异常。此示例可能是使用注释捕获的哪个。IllegalArgumentException@Test(expected = IllegalArgumentException.class)

一旦你处理了这个基本工作单元的所有用例,你就可以上一个级别。在这里,您执行完全相同的操作,但您只处理来自当前级别正下方的级别的异常。这使您的测试代码保持良好的结构,并允许您快速运行体系结构以查找出错的地方,而不必到处跳转。

处理测试的有效和错误输入

在这一点上,应该清楚我们将如何处理这些异常。有2种类型的输入:有效输入和错误输入(输入在严格意义上是有效的,但它是不正确的)。

当您使用有效输入时,您正在设置隐式期望,即您编写的任何测试都将起作用。

这样的方法调用可以如下所示:.如果此方法失败(例如:抛出异常),那么您就知道出了问题,可以开始挖掘。existingUserById_ShouldReturn_UserObject

通过添加另一个使用错误输入并期望异常的测试 (),您可以看到您的方法是否对错误的输入执行了应有的操作。nonExistingUserById_ShouldThrow_IllegalArgumentException

TL;DR

您尝试在测试中执行两项操作:检查输入是否有效和错误。通过将它分成两种方法,每种方法都做一件事,您将有更清晰的测试,并更好地了解问题所在。

通过牢记工作的分层单元,您还可以减少层次结构中较高层所需的测试量,因为您不必考虑较低层中可能出错的所有事情:当前层下面的层是您的依赖项工作的虚拟保证,如果出现问题, 它位于当前图层中(假设较低图层本身不会抛出任何错误)。


答案 2

我偶然发现了这一点,因为SonarQube的规则“squid:S2699”:“至少向这个测试用例添加一个断言。

我有一个简单的测试,其唯一目标是在不抛出异常的情况下完成。

请考虑以下简单代码:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

可以添加哪种断言来测试此方法?当然,您可以尝试使用它,但这只是代码膨胀。

解决方案来自JUnit本身。

如果没有引发异常,并且您希望显式说明此行为,只需添加,如以下示例所示:expected

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class是预期值的默认值。

如果你,你可以写:import org.junit.Test.None

@Test(expected = None.class)

您可能会发现更具可读性。


推荐