使用 JUnit 5 进行单元测试

发表于:2017-7-20 13:46

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:成富    来源:IBM

  对于所有的 Java 开发人员来说,你可以没有听说过 Spring 或是 Hibernate 框架,但是一定听说过 JUnit。JUnit 作为 Java 单元测试的鼻祖与事实上的标准,在非常多的项目中被使用。即便新兴的单元测试框架,如 TestNG 等,不断出现,JUnit 的重要性仍然是不言而喻的。目前广泛使用的是 JUnit 4 版本,而 JUnit 即将迎来它的最新版本 JUnit 5。JUnit 5 在增加了很多的新特性的同时,又保持了对 JUnit 4 的向后兼容性。本文对 JUnit 5 进行了详细的介绍。
  JUnit 5 简介
  与之前的版本不同,JUnit 5 由三个不同的模块组成。第一个模块是 JUnit 平台,其主要作用是在 JVM 上启动测试框架。它定义了一个抽象的 TestEngine API 来定义运行在平台上的测试框架,同时还支持通过命令行、Gradle 和 Maven 来运行平台。第二个模块是 JUnit Jupiter,包含了 JUnit 5 最新的编程模型和扩展机制。第三个模块是 JUnit Vintage,允许在平台上运行 JUnit 3 和 JUnit 4 的测试用例
  JUnit 5 对 Java 运行环境的最低要求是 Java 8。可以在 Eclipse 和 IntelliJ IDEA 上运行 JUnit 5 测试。本文的示例基于 IntelliJ IDEA 上开发,并使用 Gradle 作为构建工具。不过目前 IDE 对 JUnit 5 的支持还比较有限,只有最新版本的 IntelliJ IDEA 原生支持,在其它 IDE 上需要使用命令行工具来运行。
  编写测试用例
  JUnit 5 对编写单元测试用例的方式做了一系列的改进,如下介绍。
  JUnit 5 注解
  JUnit 5 提供了一些常用的注解在编写测试用例的时候使用。其中的一些注解和 JUnit 4 的注解有相同的名称,不过所在的 Java 包变成了 org.junit.jupiter.api。常用的注解见表 1。
  表 1. JUnit 5 常用注解
  清单 1 中给出了使用这些注解编写的单元测试用例。
  清单 1. 使用常用注解的单元测试用例
  @DisplayName("Calculator")
  public class CalculatorTest {
  private Calculator calculator;
  @BeforeAll
  public static void init() {
  System.out.println("Start testing");
  }
  @BeforeEach
  public void create() {
  this.calculator = new Calculator();
  }
  @AfterEach
  public void destroy() {
  this.calculator = null;
  }
  @AfterAll
  public static void cleanup() {
  System.out.println("Finish testing");
  }
  @Test
  @DisplayName("Test 1 + 2 = 3")
  public void testAdd() {
  assertEquals(3, this.calculator.add(1, 2));
  }
  @Test
  @DisplayName("Test 3 - 2 = 1")
  public void testSubtract() {
  assertEquals(1, this.calculator.subtract(3, 2));
  }
  @Disabled
  @Test
  @DisplayName("disabled test")
  public void ignoredTest() {
  System.out.println("This test is disabled");
  }
  }
  在这些注解中,最实用的应该是@DisplayName。通过@DisplayName,开发人员可以为每个测试用例添加更具体的名字,更容易传达用例所要测试的内容。
  通过@Tag 注解可以为测试类或方法添加标签,但是不同的标签只是通过字符串来进行区分,并不是类型安全的。一个拼写错误就可能造成标签没有被正确应用。更好的做法是使用类型安全的元注解(meta annotation)。编译器会对元注解标签的正确性进行验证,从而减少无意的错误。清单 2 中定义了元注解标签@Remote,对应于标签 remote。
  清单 2. 元注解标签
  @Target({ ElementType.TYPE, ElementType.METHOD })
  @Retention(RetentionPolicy.RUNTIME)
  @Tag("remote")
  public @interface Remote {
  }
  清单 3 中展示了@Remote 的用法。使用@Remote 的作用等同于@Tag("Remote"),为 testGetUser 方法添加了标签 remote。使用@Remote 不仅提高了代码的可读性,也可以避免无意的拼写错误带来的问题。
  清单 3. 使用元注解标签
  @DisplayName("Remote test")
  public class RemoteTest {
  @Test
  @Remote
  public void testGetUser() {
  System.out.println("Get user");
  }
  }
  JUnit 5 断言
  断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
  第一类是简单断言,用来对单个值进行简单的验证,常用的方法见表 2。
  表 2. 常用的断言方法
  这些方法都有多个重载方法,可以提供额外的消息来作为断言不满足时的提示消息,还可以接受 Java 8 中的 Supplier 接口来获取要判断的值和显示的消息。清单 4 中给出了简单断言的使用示例。
  清单 4. 简单断言
  @Test
  @DisplayName("simple assertion")
  public void simple() {
  assertEquals(3, 1 + 2, "simple math");
  assertNotEquals(3, 1 + 1);
  assertNotSame(new Object(), new Object());
  Object obj = new Object();
  assertSame(obj, obj);
  assertFalse(1 > 2);
  assertTrue(1 < 2);
  assertNull(null);
  assertNotNull(new Object());
  }
  第二类是通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等,如清单 5 所示。
  清单 5. assertArrayEquals 方法的示例
  @Test
  @DisplayName("array assertion")
  public void array() {
  assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
  }
  第三类是通过 assertAll 方法来判断一组断言是否满足。assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言,如清单 6 所示。
  清单 6. assertAll 方法的示例
  @Test
  @DisplayName("assert all")
  public void all() {
  assertAll("Math",
  () -> assertEquals(2, 1 + 1),
  () -> assertTrue(1 > 0)
  );
  }
  第四类是通过 assertThrows 或 expectThrows 来判断是否抛出期望的异常类型。两个方法的参数都是所期望的异常类型和对应的 Executable 接口的实现对象,区别在于 expectThrows 方法会返回抛出的异常对象。在清单 7 中,1/0 会抛出 ArithmeticException 异常,assertThrows 用来验证这一点。
  清单 7. assertThrows 和 expectThrows 方法的示例
  @Test
  @DisplayName("throws exception")
  public void exception() {
  assertThrows(ArithmeticException.class, () -> System.out.println(1 / 0));
  }
  第五类是 fail 方法,用来使一个测试方法失败。清单 8 中的测试会直接失败。
  清单 8. 通过 fail 方法直接使得测试失败
  @Test
  @DisplayName("fail")
  public void shouldFail() {
  fail("This should fail");
  }
  JUnit 5 前置条件
  JUnit 5 中的前置条件(assumptions)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。在清单 9 中,assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
  清单 9. JUnit 5 前置条件
  @DisplayName("Assumptions")
  public class AssumptionsTest {
  private final String environment = "DEV";
  @Test
  @DisplayName("simple")
  public void simpleAssume() {
  assumeTrue(Objects.equals(this.environment, "DEV"));
  assumeFalse(() -> Objects.equals(this.environment, "PROD"));
  }
  @Test
  @DisplayName("assume then do")
  public void assumeThenDo() {
  assumingThat(
  Objects.equals(this.environment, "DEV"),
  () -> System.out.println("In DEV")
  );
  }
  }
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号