我知道这样做的一种方法是:
@Test public void foo() { try { // execute code that you expect not to throw Exceptions. } catch(Exception e) { fail("Should not have thrown any exception"); } }
有没有更清洁的方法来做到这一点?(可能使用 Junit 的@Rule?)
@Rule
你以错误的方式接近这个。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部变为绿色。
我注意到这个问题不时引起人们的兴趣,所以我会扩大一点。
当您进行单元测试时,向您自己定义您认为的工作单元非常重要。基本上:提取您的代码库,可能包含也可能不包含表示单个功能的多个方法或类。
或者,如Roy Osherove 的 The art of Unit Testing, 2nd Edition 中所定义,第 11 页:
单元测试 是一段自动化的代码,它调用正在测试的工作单元,然后检查关于该单元的单个最终结果的一些假设。单元测试几乎总是使用单元测试框架编写的。它可以轻松编写并快速运行。它是可信赖的、可读的和可维护的。只要生产代码没有改变,它的结果是一致的。
重要的是要意识到一个 工作单元 通常不仅仅是一种方法,而是在最基本的层面上它是一种方法,然后它被另一个工作单元封装。
理想情况下,您应该对每个单独的工作单元都有一个测试方法,这样您就可以随时查看哪里出了问题。在此示例中,调用了一个基本方法getUserById(),该方法将返回一个用户,并且总共有 3 个工作单元。
getUserById()
第一个工作单元应该测试在有效和无效输入的情况下是否返回了有效用户。 数据源抛出的任何异常都必须在这里处理:如果没有用户存在,则应该有一个测试来证明当找不到用户时抛出了异常。其中一个示例可能是注释IllegalArgumentException中捕获的示例。@Test(expected = IllegalArgumentException.class)
IllegalArgumentException
@Test(expected = IllegalArgumentException.class)
一旦你处理了这个基本工作单元的所有用例,你就提升了一个层次。在这里,您所做的完全相同,但您只处理来自当前级别正下方的异常。这可以使您的测试代码保持良好的结构,并允许您快速运行架构以找到问题所在,而不必到处乱跳。
此时应该清楚我们将如何处理这些异常。输入有两种类型: 有效 输入和 错误 输入(严格意义上的输入有效,但不正确)。
当您使用 有效 输入时,您正在设置隐含的期望,即无论您编写什么测试都将起作用。
这样的方法调用可能如下所示:existingUserById_ShouldReturn_UserObject. 如果此方法失败(例如:抛出异常),那么您就知道出了问题,您可以开始挖掘。
existingUserById_ShouldReturn_UserObject
通过添加另一个nonExistingUserById_ShouldThrow_IllegalArgumentException使用 错误 输入并期望异常的测试 ( ),您可以查看您的方法是否按照错误输入的预期执行操作。
nonExistingUserById_ShouldThrow_IllegalArgumentException
您试图在测试中做两件事:检查有效和错误的输入。通过将其分成两种方法,每种方法都做一件事,您将获得更清晰的测试,并更好地了解哪里出了问题。
通过牢记分层的工作单元,您还可以减少层次结构中较高层所需的测试量,因为您不必考虑较低层中可能出错的所有事情:当前层之下的层是您的依赖项工作的虚拟保证,如果出现问题,它在您的当前层中(假设较低层本身不会引发任何错误)。