本文假定读者有单元测试基础,不会对单元测试的概念做过多的介绍,主要讲解junit5的新功能用法,让读者快速上手Junit5。如果你想了解单元测试的基础概念可以阅读文章:一文搞定单元测试核心概念

注意:建议大家使用文章中推荐的jdk、Eclipse、mvn以及 pom.xml进行配置,可以确保大家代码的顺利运行!

JUnit5 框架构成

Junit5需要Java 8或更高版本,和 Junit4 只是一个单独的 Jar 包不同,目前的 Junit5 组成如下:

JUnit 5= JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform是 Junit 向测试平台演进,提供平台功能的模块,通过 JUnit Platform,其他的自动化测试引擎或开发人员自己定制的引擎都可以接入Junit 实现对接和执行

JUnit Jupiter, 这是 Junit5 的核心,可以看作是承载 Junit4 原有功能的演进,它包含了很多丰富的新特性来使 JUnit 自动化测试更加方便、功能更加丰富和强大。本系列就会重点围绕 Jupiter 中的一些特性进行介绍。Jupiter 本身也是一个基于 Junit Platform 的引擎实现。

JUnit Vintage,Junit发展了10数年,Junit 3 和 Junit 4 都积累了大量的用户,作为新一代框架,这个模块是对 JUnit3,JUnit4 版本兼容的测试引擎,使旧版本 junit 的自动化测试脚本也可以顺畅运行在 Junit5 下,它也可以看作是基于Junit Platform 实现的引擎范例。

Eclipse环境搭建

Eclipse从Oxygen.1a(4.7.1a) 开始支持Junit5

在Eclipse的 Marketplace中搜索Junit5,然后安全JUnit-Tools 如下图所示:

从0到1上手JUnit5_java

安装插件后,会在eclipse中自动装入junit5 Library,如下图:

从0到1上手JUnit5_junit_02

Maven,强烈推荐如下配置!

<properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <maven.compiler.source>1.8</maven.compiler.source>
              <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
              <junit.jupiter.version>5.5.2</junit.jupiter.version>
              <junit.platform.version>1.5.2</junit.platform.version>
       </properties>
       <dependencies>
              <dependency>
                     <groupId>org.junit.jupiter</groupId>
                     <artifactId>junit-jupiter-engine</artifactId>
                     <version>${junit.jupiter.version}</version>
                     <scope>test</scope>
              </dependency>
              <dependency>
                     <groupId>org.junit.platform</groupId>
                     <artifactId>junit-platform-runner</artifactId>
                     <version>${junit.platform.version}</version>
                     <scope>test</scope>
              </dependency>
       </dependencies>
       <build>
              <plugins>
                     <plugin>
                            <artifactId>maven-compiler-plugin</artifactId>
                            <version>3.8.1</version>
                     </plugin>
                     <plugin>
                            <artifactId>maven-surefire-plugin</artifactId>
                            <version>2.22.2</version>
                     </plugin>
              </plugins>
       </build>

新增的断言

assertAll:用来校验所有断言是否为真。如下代码会导致校验失败

assertAll("person",
                       () ->assertEquals("John","John"),
                       () ->assertEquals("Doe","kevin")
              );
assertTimeout:断言在超出给定超时之前,所提供的可执行代码块的执行完成。
assertTimeout(ofSeconds(1),() -> {
                     // Simulate task that takes morethan 10ms.
                     Thread.sleep(2000);
                });

四个变化的注解

@BeforeAll 只执行一次,执行时机是在所有测试和@BeforeEach 注解方法之前。

@BeforeEach 在每个测试执行之前执行。

@AfterEach 在每个测试执行之后执行。

@AfterAll 只执行一次,执行时机是在所有测试和 @AfterEach 注解方法之后。

新增的实用标签,在指定条件下运行用例

新增Enabled相关标签,表示在指定系统、指定jdk版本等等条件下运行。详情如下:

用例只在指定系统中运行

@EnabledOnOs({ LINUX, MAC })

用例只在指定JDK版本时运行

@EnabledOnJre(JAVA_8)

用例只能在64位操作系统上执行的测试

@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")

需要传入环境变量DEBUG=true才能执行的测试@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")

用例显示自定义名称

@DisplayName("测试用例1")

例如设置

@Test
         @DisplayName("测试用例1")
           void succeedingTest() {
                assertAll("person",
                       () ->assertEquals("John","John"),
                       () ->assertEquals("Doe","kevin")
               );
                   }

在执行结果中显示

从0到1上手JUnit5_单元测试_03

用例无效

@Disabled

重复执行用例

@RepeatedTest(10)

用例超时

@Timeout(5)

嵌套

@Nested设计目的就是在测试类中嵌套其他测试类用以表明用例组之间的关系,个人感觉应用场景有限。

例如:

class NestedDemo {
    @Test
    void case1() {
      assertTrue(true);
    }
    @Nested
    class NewTest {
        @Test
        void caseN1() {
           assertTrue(true);
           }
        @Test   
        void caseN2() {
            assertTrue(false);
        }
        @Test
        void caseN3() {
            assertTrue(true);}
 
    }
}

运行时会让你选择执行哪个测试类

参数化

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba"})
void palindromes(Stringcandidate) {
    assertTrue(StringUtils.isPalindrome(candidate));
}
 
@ParameterizedTest
@CsvSource({
    "apple,         1",
    "banana,        2",
    "'lemon, lime', 0xF1"
})
void testWithCsvSource(String fruit, intrank) {
    assertNotNull(fruit);
    assertNotEquals(0, rank);
}

@ValueSource:声明一个基本类型的数组(String、int、long、double等),并且为测试方法提供调用参数,每个参数执行一次测试方法。

@EnumSource:为测试方法提供Enum常量参数,这个注释提供一个可选的name参数,通过其指定使用那些常量,如:names = {"DAY", "HOURS"}。还有一个可选的mode参数,能够通过names中声明的常量列表或者正则表达式来细粒度地控制那些常量将会被传递到测试方法中。

@MethodSource:可是通过这个注释引用测试类中的一个或者多个工厂方法,这些方法必须返回一个Stream、Interable、Iterator或者数组参数,工厂方法不能接收任何参数。默认情况下必须是static方法,除非声明了@TestInstace(Lifecycle.PER_CLASS)。如果测试方法中有多个参数,则需要返回一个Arguments实例的集合。

@CsvSource:在参数化测试方法中,通过参数列表声明参数,每组参数的不同值用逗号隔开。

@CsvFileSource:使用类路径中的CSV文件作为参数,每一行数据都会触发参数化测试的一次调用。

@ArgumentsSource:指定一个实现ArgumentsProvider接口的参数提供类作为参数化测试方法的参数提供者,这个类的provideArguments方法返回的Stream作为参数流。

用例执行顺序

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo{
    @Test
    @Order(1)
    voidnullValues() {
        // perform assertions against null values
    }
    @Test
    @Order(2)
    voidemptyValues() {
        // perform assertions against empty values
    }
    @Test
    @Order(3)
    voidvalidValues() {
        // perform assertions against valid values
    }
}

自定义标签的使用

标签生成

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("smoke")
@Test
public @interface Smoke{}

用例中使用标签

import staticorg.junit.Assert.assertTrue;
import org.junit.Test;
import com.my.demo.Smoke;
public class TestTagDemo {
       @Test
       @Smoke
       public void case1() {
              System.out.println("case1");
           assertTrue(true);
       }
       @Test
       @Smoke
       public void case2() {
              System.out.println("case2");
           assertTrue(false);
       }
       @Test
       public void case3() {
              System.out.println("case3");
           assertTrue(false);
       }
}

运行带标签的用例

Run As>Run Configuration

从0到1上手JUnit5_单元测试_04

运行结果

从0到1上手JUnit5_单元测试_05

如上图所示,只运行了@Somke标签的用例

maven-surefire-plugin中运行

在pom.xml文件中配置

<plugins>
  <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M4</version>
                <configuration>
                <groups>smoke</groups>
            </configuration>
</plugin>

注意,想要通过maven构建的时候执行用例,用例命名必须遵循如下格式:

 **/Test*.java

 **/*Test.java

 **/*Tests.java

 **/*TestCase.java

Suite

在JUnit 5 中使用@RunWith,@SelectPackages和@SelectClasses作为suite的常用方法。还可以配合@IncludeTags,执行需要具有某标签的用例。

代码如下所示:

@RunWith(JUnitPlatform.class)
@SelectClasses( ClassATest.class ) //执行某个类
@SelectPackages({"com.examples.packageA","com.examples.packageB"})
//执行某个包中的所有用例
public class JUnit5TestSuiteExample
{
}

注意:我在实际操作中发现在IDE中使用junit5,对其版本信息要求极高,强烈建议大家

使用如下配置(本机测试通过)

推荐jdk:1.8.0_151(1.8即可)

推荐Eclipse:Photon Release (4.8.0)

推荐mvn版本:3.6.3

推荐 pom配置

<properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <maven.compiler.source>1.8</maven.compiler.source>
              <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
              <junit.jupiter.version>5.5.2</junit.jupiter.version>
              <junit.platform.version>1.5.2</junit.platform.version>
       </properties>
       <dependencies>
              <dependency>
                     <groupId>org.junit.jupiter</groupId>
                     <artifactId>junit-jupiter-engine</artifactId>
                     <version>${junit.jupiter.version}</version>
                     <scope>test</scope>
              </dependency>
              <dependency>
                     <groupId>org.junit.platform</groupId>
                     <artifactId>junit-platform-runner</artifactId>
                     <version>${junit.platform.version}</version>
                     <scope>test</scope>
              </dependency>
       </dependencies>
       <build>
              <plugins>
                     <plugin>
                            <artifactId>maven-compiler-plugin</artifactId>
                            <version>3.8.1</version>
                     </plugin>
                     <plugin>
                            <artifactId>maven-surefire-plugin</artifactId>
                            <version>2.22.2</version>
                     </plugin>
              </plugins>
       </build>
</project>

动态测试

注解@Test可以认为是静态测试方法,是在编译时指定的。在JUnit5中还有一类测试称为动态测试,可以在运行时改变。

@TestFactory:声明动态测试,声明的方法本身不是测试用例,而是生成测试用例的工厂方法。这个方法必须返回一个DynamicNode实例的Stream、Collection、Iterable或Iterator。DynamicNode有两个可实例化子类DynamicContainer和DynamicTest。动态测试的执行生命周期和@Test测试不同,同一个@TestFactory方法所生成的n个动态测试,@BeforeEach和@AfterEach只会在n个动态测试开始前和结束后执行一次,不会为每个单独的动态测试都执行。个人感觉应用场景有限,这里就不做详细介绍了。

总结

大家可以看到Junit5跟Junit4比有了很大的变化,个人认为其主要目的是对标TestNG,可以说Junit5跟TestNG更像了!本文内容很多,建议大家只需要有个大概的印象即可,在实际工作中如果使用了Junit5在或过头来仔细阅读并结合实际应用!原创不易,如果文章帮助了大家,欢迎转发!