原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (十一)JUnit概述

799 阅读6分钟

上一章我们分享了“使用JUnit、AssertJ和Mockito编写单元测试需要准备好的开发环境 ”。这一章我们来讲讲“什么是JUnit”。 Never in the field of software development was so much owed by so many to so few lines of code. 软件开发史上从未有过如此少量的代码对如此广阔的领域产生了如此巨大的作用。 --Martin Fowler 1997年,Eric Gamma(设计模式“四人帮”之一,Eclipse之父)和Kent Beck(极限编程方法学创始人)在从瑞士苏黎世飞往美国亚特兰大的飞机上偶遇,因为长路漫漫无心睡眠,两个人就进行结对编程,在下飞机之前完成了JUnit这个测试框架的第一个版本。此后,JUnit迅速传播,成为几乎每个Java项目的标配。 JUnit超级流行,是事实上的Java单元测试和TDD的工具标准。有人在2016年选择了GitHub上最流行的3862个Java项目,找出了最常用的100个组件,可以看到JUnit 以63%的采用率遥遥领先于其他的组件: 知名的项目都有很高的单元测试覆盖率。而JUnit是Java领域单元测试的事实上的标准。另一个可以与JUnit抗衡的测试框架是TestNG,但只有不到10%的采用率。

JUnit 概述

JUnit是事实上的Java测试框架标准。至今JUnit已经发布了第5个版本。与以前的版本相比,JUnit5作出了大量的改进。本教程也是以版本5为基准介绍JUnit的使用。 JUnit 5需要JDK 8.0以上版本。

1. JUnit 5的组成

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage JUnit 5主要由三大部分组成:
  • JUnit Platform:为在JVM上启动测试框架提供基础。它还定义了TestEngine API, 用来开发在平台上运行的测试框架。此外,该平台还提供了一个用于从命令行启动该平台的控制台启动器和一个基于JUnit 4的运行器,用于在基于JUnit 4的环境中在该平台上运行任何TestEngine。它也为流行的IDE(IntelliJ IDEA、Eclipse、NetBeans、Visual Studio Code等)和构建工具(Gradle、Maven、Ant等)提供了一级的支持。
  • JUnit Jupiter:是在JUnit 5中编写测试和扩展的新型编程模型[扩展模型的组合。Jupiter子项目提供了用于在平台上运行基于Jupiter的测试的TestEngine
  • JUnit Vintage:提供用于在平台上运行基于JUnit 3和JUnit 4的测试的TestEngine
如果我们不需要和JUnit版本3或者4兼容,就不需要JUnit Vintage。 下面是JUnit 5的模块依赖关系图:

2. 测试类与测试方法

测试类是同时符合下述条件的类:
  • 至少包含一个测试方法。
  • 是顶层类、静态成员类或标记为@Nested的成员类。
  • 不是抽象类。
  • 不是private类。
  • 只有一个构造函数
测试方法是同时符合下述条件的方法:
  • 被标记或元标记为@Test,@RepeatedTest ,@ParameterizedTest,@TestFactory, @TestTemplate之一。
  • 是实例方法,不是静态方法。
  • 不是抽象方法。
  • 不是private方法。
  • 没有返回值。
  • 一般没有参数。特殊情况下有参数(后面的章节会谈到这个)。
测试类可以从超类或接口上继承测试方法。

3. 测试实例生命周期

JUnit测试实例生命周期有两种:
  • PER_METHOD 这是JUnit默认的生命周期。具体来说,就是在执行测试时为测试类中的每个测试方法单独创建测试类的实例,执行测试方法后销毁这个测试类实例,重新创建一个新的测试类实例来执行余下的测试方法。
  • PER_CLASS 在PER_CLASS模式下,JUnit在执行测试时创建一个测试类实例,在这个实例上执行所有的测试方法。
一般情况下应该采用采用PERMETHOD生命周期,因为它的隔离性更好。如果采用PERCLASS生命周期,一个测试的执行可能更改测试类的内部状态(修改了字段值),从而影响了后面的测试的先决条件。 PERCLASS模式比默认的PERMETHOD模式有一些额外的好处。具体来说,使用PER_CLASS模式,可以在非静态方法和接口默认方法上声明@BeforeAll和@AfterAll。因此,”per-class”模式也可以在@Nested测试类中使用@BeforeAll和@AfterAll方法。 要设定PER_CLASS生命周期,有两种可行方式:
  • 对测试类加入@TestInstance(Lifecycle.PER_CLASS)注解。这种方式只影响当前测试类。
  • 修改junit-platform.properties全局配置文件,加入下面的内容:
    junit.jupiter.testinstance.lifecycle.default = per_class
    这种方式将影响整个测试计划中的所有测试类。
如果既有注解,又有全局配置,则以注解为优先。

4. JUnit全局配置

可以在测试类路径根目录下创建一个junit-platform.properties文本文件,用来定义JUnit平台的默认配置。在Maven和Gradle项目中,这个文件的位置通常是:
src/test/resources/junit-platform.properties
具体的配置选项在后面的章节中讲述。

5. 注解(Annotations)

JUnit是由Annotation驱动的。编写测试的时候,通常只需要使用JUnit定义的Annotation来标记测试类、方法、字段和参数,不需要继承JUnit特定的超类,也不需要实现JUnit特定的接口。多数情况下,也不需要使用JUnit定义的类和接口。

5.1 核心注解

JUnit Jupiter支持下列注解,用于配置测试和扩展框架。 除非特别说明,所有核心注解位于junit-jupiter-api模块中的org.junit.jupiter.api包中。

5.2 元注解(Meta-Annotations)与组合注解(Composed Annotations)

JUnit的注解可以作为元注解使用,这意味着你可以定义自己的组合注解,它们自动继承它们的元注解的语义。 例如,您可以像下面那样创建一个名为@Fast的自定义组合注释,而不必在整个代码库(请参阅标签和过滤)中复制和粘贴@Tag("fast")。然后@Fast可以用作@Tag("fast")的一个替代品。
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.junit.jupiter.api.Tag;@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Tag("fast")public @interface Fast {}
下面的测试方法显示如何使用@Fast注解:
@Fast@Testvoid myFastTest() {    // ...}
标记了@Fast注解的测试方法,等价于标记了@Tag("fast")注解。 你甚至可以定义一个同时用@Tag("fast")和@Test注解的@FastTest组合注解,用来在测试方法中取代上述两个注解:
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.junit.jupiter.api.Tag;import org.junit.jupiter.api.Test;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Tag("fast")@Testpublic @interface FastTest {}
JUnit自动识别下面的测试方法为@Test和@Tag("fast"):
@FastTestvoid myFastTest() {    // ...}

6. 运行测试

如何运行测试在后面的章节中有具体表述。在各种IDE中通常都支持针对测试类鼠标右键弹出的上下文菜单中执行单元测试。下面是在IntelliJ IDEA中的图示:

下面是Eclipse中的图示:

这一章我们就讲到这里,下一章我们讲讲“如何使用JUnit编写测试”!

- THE END -

原创作者 | 杨宇Yangyu

编程道与术原创内容

转载请注明“编程道与术”出处

=   往期热门内容  =