如何使用 JUnit5 框架?

2023年 10月 5日 60.9k 0

JUnit5 单元测试框架使用教程

一、Junit5 是什么?

  Junit5是一个用于在Java平台上进行单元测试的框架。JUnit 5 框架主要由三部分组成:JUnit Platform、JUnit Jupiter 和 JUnit Vintage。

  • JUnit Platform:定义了测试引擎的 API,是 JVM 上用于启动测试框架的基础服务,支持通过 IDE、构建工具、命令行等方式运行单元测试。
  • JUnit Jupiter:包含 JUnit 5 新的编程模型和扩展模型,主要用于编写和扩展测试代码。
  • JUnit Vintage:兼容运行 JUnit 3 和 JUnit4 编写的测试用例。

二、Junit5 的注解

(一)导入依赖

  导入五个依赖:

        
        
            org.junit.jupiter
            junit-jupiter-api
            5.10.0
        
            
        
        
            org.junit.jupiter
            junit-jupiter-params
            5.10.0
        
            
        
        
            org.junit.jupiter
            junit-jupiter-engine
            5.10.0
            test
        

        
        
            org.junit.platform
            junit-platform-suite-api
            1.10.0
        
            
        
        
            org.junit.platform
            junit-platform-suite-engine
            1.10.0
            test
        

(二)常用的注解

如果你的IDEA在使用JUnit注解的时候发生如下情况:依赖已经导入且加载完成,但是IDEA没能识别出来,如图:

image.png

有的注解也在params包中。

image.png

(我真的不理解为啥)

有一种解决办法:

image.png

image.png

image.png

点击后,选择相应的版本,我这里是5.10.0,点了之后IDEA就能识别出来了。

1.@Test

   @Test 表示方法是测试方法,有多个 @Test 方法就执行多少个。

public class JUnitTest {

    @Test
    void test0(){
        System.out.println("测试用例1");
    }

    @Test
    void test1(){
        System.out.println("测试用例2");
    }

    @Test
    void test2(){
        System.out.println("测试用例3");
    }
}

结果:

image.png

2.@BeforeAll、@AfterAll

  • @BeforeAll:表示被注解的方法应该在当前类的所有@Test,@RepeatedTest,@ParameterizedTest和@TestFactory方法之前执行;
  • @AfterAll:表示被注解的方法应该在当前类的所有@Test,@RepeatedTest,@ParameterizedTest和@TestFactory方法之后执行;

使用@BeforeAll@AfterAll注解的方法要加上static

public class JUnitTest {
    @BeforeAll
    static void beforeAll(){
        //可以用于创建一些资源
        System.out.println("我是BeforeAll,我最开始执行。");
    }
    @AfterAll
    static void afterAll(){
        //可以用于释放资源
        System.out.println("我是AfterAll,我最后执行。");
    }
    @Test
    void test0(){
        System.out.println("测试用例1");
    }
    @Test
    void test1(){
        System.out.println("测试用例2");
    }
    @Test
    void test2(){
        System.out.println("测试用例3");
    }
}

image.png

3.@BeforeEach、@AfterEach

  • @BeforeEach:表示被注解的方法应在当前类的每个@Test,@RepeatedTest,@ParameterizedTest或@TestFactory方法之前执行;
  • @AfterEach:表示被注解的方法应在当前类的每个@Test,@RepeatedTest,@ParameterizedTest或@TestFactory方法之后执行;
public class JUnitTest {
    @BeforeAll
    static void beforeAll(){
        System.out.println("我是BeforeAll,我最开始执行。");
    }
    @AfterAll
    static void afterAll(){
        System.out.println("我是AfterAll,我最后执行。");
    }
    @BeforeEach
    void beforeEach(){
        System.out.println("我是BeforeEach,我在每个 @Test 前执行。");
    }
    @AfterEach
    void afterEach(){
        System.out.println("我是AfterEach,我在每个 @Test 后执行。");
    }
    @Test
    void test0(){
        System.out.println("测试用例1");
    }
    @Test
    void test1(){
        System.out.println("测试用例2");
    }
    @Test
    void test2(){
        System.out.println("测试用例3");
    }
}

结果:

image.png

4.@Disabled

  @Disabled用于禁用测试类或测试方法,添加该注解的方法不会被测试。

public class JUnitTest {
    @BeforeAll
    static void beforeAll(){
        System.out.println("我是BeforeAll,我最开始执行。");
    }
    @AfterAll
    static void afterAll(){
        System.out.println("我是AfterAll,我最后执行。");
    }
    @BeforeEach
    void beforeEach(){
        System.out.println("我是BeforeEach,我在每个 @Test 前执行。");
    }
    @AfterEach
    void afterEach(){
        System.out.println("我是AfterEach,我在每个 @Test 后执行。");
    }
    @Test
    @Disabled //忽略测试用例1
    void test0(){
        System.out.println("测试用例1");
    }
    @Test
    void test1(){
        System.out.println("测试用例2");
    }
    @Test
    void test2(){
        System.out.println("测试用例3");
    }
}

结果:

image.png

(三)参数化测试

1.@ParameterizedTest + @ValueSource

  @ParameterizedTest的作用就是可以用不同的参数多次运行测试。但是必须声调用提供参数的来源(source)。

  @ValueSource它可以让你指定一个原生类型(String,int,long或double)的数组,并且只能为每次调用提供一个参数。

public class JUnitTest {

    @BeforeAll
    static void beforeAll(){
        System.out.println("我是BeforeAll,我最开始执行。");
    }

    @AfterAll
    static void afterAll(){
        System.out.println("我是AfterAll,我最后执行。");
    }

    @BeforeEach
    void beforeEach(){
        System.out.println("我是BeforeEach,我在每个 Test 前执行。");
    }

    @AfterEach
    void afterEach(){
        System.out.println("我是AfterEach,我在每个 Test 后执行。");
    }

    @Test
    void test0(){
        System.out.println("测试用例1");
    }

    @Test
    void test1(){
        System.out.println("测试用例2");
    }

    @Test
    void test2(){
        System.out.println("测试用例3");
    }
    @ParameterizedTest
    @ValueSource(strings = {"小明","小红","小兰"})
    void paramTest(String name){
        System.out.println(name);
    }
}

结果:

image.png

2.@ParameterizedTest + @CsvSource

  @CsvSource允许将参数列表表示为以逗号分隔的值(例如,字符串文字)。

public class JUnitTest { 
    @ParameterizedTest
    @CsvSource({"小明, 1","小红,2","小兰,3"})
    void csvSource(String name,int id){
        System.out.println(name + ":" + id);
    }
}

结果:

image.png
@CsvSource使用'作为转义字符。

示例输入 结果字符列表
@CsvSource({ "foo, bar" }) "foo", "bar"
@CsvSource({ "foo, 'baz, qux'" }) "foo", "baz, qux"
@CsvSource({ "foo, ''" }) "foo", ""
@CsvSource({ "foo, " }) "foo", null

3.@ParameterizedTest + @CsvFileSource

  @CsvFileSource让你使用classpath中的CSV文件。CSV文件中的每一行都会导致参数化测试的一次调用。

resources目录下创建csv文件:

test.csv:

小明, 1
小红, 2
"小明, 小红", 3
public class JUnitTest {
    @ParameterizedTest
    @CsvFileSource(resources = "/test.csv")
    void csvFile(String name,int id){
        System.out.println(name + ": " + id);
    }
}

结果:

image.png

@CsvSource中使用的语法相反,@CsvFileSource使用双引号"作为转义字符。通过上面的代码就可以看出来。一个空的转义值""会产生一个空字符串, 一个完全为空的值被解释为null引用。

4.@ParameterizedTest + @MethodSource

  @MethodSource允许引用一个或多个测试类的工厂方法。

public class JUnitTest {
    @ParameterizedTest
    @MethodSource("stringProvider") //指定方法
    void methodSource(int age,String name){
        System.out.println(age + ": " + name);
    }
    static Stream stringProvider() {
        return Stream.of(
                Arguments.arguments(12,"李四"),
                Arguments.arguments(18,"王五"),
                Arguments.arguments(20,"小红")
        );
    }
}

  @MethodSource注解表示这个方法的参数来源于一个名为stringProvider的静态方法。stringProvider方法返回一个Stream类型的对象,其中每个Arguments对象包含了一组用于测试的参数。

image.png

(四)测试方法的执行顺序

1.@TestMethodOrder + @Order

  在 JUnit5 中,测试方法执行的顺序是不确定的或者是根据方法首字母来排序的。

public class JUnitTest2 {
    @Test
    void C(){
        System.out.println("A");
    }
    @Test
    void B(){
        System.out.println("B");
    }
    @Test
    void A(){
        System.out.println("C");
    }
}

结果:

image.png

  让执行顺序为 C、B、A:

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class JUnitTest2 {
    int a = 0;
    @Test
    @Order(1)
    void C(){
        a++;
        System.out.println(a);
        System.out.println("C");
    }
    @Test
    @Order(2)
    void B(){
        a++;
        System.out.println(a);
        System.out.println("B");
    }
    @Test
    @Order(3)
    void A(){
        a++;
        System.out.println(a);
        System.out.println("A");
    }
}

  首先在类上添加@TestMethodOrder(MethodOrderer.OrderAnnotation.class),然后再为每个方法上添加@Order()注解,值越小越优先被执行。

(五)测试实例的生命周期

1.@TestInstance

  我添加一个成员变量,每次执行测试方法的时候都++一次。

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
public class JUnitTest2 {
    int a = 0;

    @Test
    @Order(1)
    void A(){
        a++;
        System.out.println("A方法:" + a);
        System.out.println("A");
    }

    @Test
    @Order(2)
    void B(){
        a++;
        System.out.println("B方法:" + a);
        System.out.println("B");
    }

    @Test
    @Order(3)
    void C(){
        a++;
        System.out.println("C方法:" + a);
        System.out.println("C");
    }
}

结果:

image.png

  为了允许隔离执行单个的测试方法,JUnit在执行每个测试方法之前会创建每个测试类的新实例。如果想改变策略,就要用@TestInstance,在类上添加@TestInstance(TestInstance.Lifecycle.PER_CLASS)

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class JUnitTest2 {
    int a = 0;

    @Test
    @Order(1)
    void A(){
        a++;
        System.out.println("A方法:" + a);
        System.out.println("A");
    }

    @Test
    @Order(2)
    void B(){
        a++;
        System.out.println("B方法:" + a);
        System.out.println("B");
    }

    @Test
    @Order(3)
    void C(){
        a++;
        System.out.println("C方法:" + a);
        System.out.println("C");
    }
}

结果:

image.png

Lifecycle.PER_CLASS表示只创建一个实例。不添加注解的时候,默认是Lifecycle.PER_METHOD

  当使用这种模式时,每个测试类将创建一个新的测试实例。因此,如果测试方法依赖于存储在实例变量中的状态,则可能需要在@BeforeEach@AfterEach方法中重置该状态(重置变量的值)。

(六)断言 Assertions

断言方法 描述
assertEquals(expected, actual) 检查两个值是否相等,如果不相等则抛出AssertionError
assertNotEquals(expected, actual) 检查两个值是否不相等,如果相等则抛出AssertionError
assertTrue(condition) 检查一个条件是否为真,如果为假则抛出AssertionError
assertFalse(condition) 检查一个条件是否为假,如果为真则抛出AssertionError
assertNull(object) 检查一个对象是否为null,如果不为null则抛出AssertionError
assertNotNull(object) 检查一个对象是否不为null,如果为null则抛出AssertionError
assertSame(expected, actual) 检查两个对象是否是同一个实例,如果不是则抛出AssertionError
assertNotSame(expected, actual) 检查两个对象是否不是同一个实例,如果是则抛出AssertionError
assertArrayEquals(expected, actual) 检查两个数组是否相等,如果不相等则抛出AssertionError
assertTimeout(duration, executable) 检查一个可执行的代码块是否在指定的时间内完成,如果超时则抛出AssertionError
public class JUnitTest3 {
    @Test
    void assertEqualsDemo(){
        int num = 10;
        Assertions.assertEquals(1,num,"不符合预期");
    }
    @Test
    void assertTrueDemo(){
        int num = 10;
        Assertions.assertTrue(num > 10,"不符合预期");
    }
    @Test
    void assertTimeoutDemo(){
        int num = 10;
        Assertions.assertTimeout(Duration.ofSeconds(3), new Executable() {
            @Override
            public void execute() throws Throwable {
                //代码块
                Thread.sleep(4000);
            }
        });
    }
}

结果:

image.png

(七)测试套件

  测试套件是一组相关的测试,可以一起运行,以便更方便地组织和管理测试。使用套件要引入两个依赖:junit-platform-suite-apijunit-platform-suite-engine,具体的在文章开头。

  套件其实很好理解,就是使几个类同时进行测试。

1.@SelectClasses

@Suite
@SelectClasses(value = {JUnitTest.class,JUnitTest2.class})
public class RunSuite {

}

@Suite的作用是将一个类标记为JUnit平台上的测试套件。

@SelectClasses指定在JUnit平台上运行测试套件时要选择的类。

运行结果:

image.png

2.@SelectPackages

  可以选择类,那么也可以包。

@Suite
@SelectPackages(value = {"package1"})
//可以选择多个包:@SelectPackages(value = {"package1","package2","package3"……})
public class RunSuite {

}

image.png

结果:

image.png
为什么只执行了JUnitTest这一个类?我的JUnitTest2呢?我们来看看它:

image.png

IDEA提示我们它的命名不符合规则,那这个规则是什么意思呢?

3.测试类命名规则

  • [A-Z[A-Za-zd]*Test(s|Case)?:表示以大写字母开头,后面跟任意个字母或数字,最后以Test, Tests, TestCase结尾的字符串,例如MyTest, MyTests, MyTestCase等。
  • Test[A-Z[A-Za-zd]*:表示以Test开头,后面跟一个大写字母,再后面跟任意个字母或数字的字符串,例如TestMyClass, TestMyMethod等。
  • IT(.*):表示以IT开头,后面跟任意个任意字符的字符串,例如ITMyClass, ITMyMethod等。
  • (.*)IT(Case)?:表示以任意个任意字符开头,后面跟IT或者ITCase的字符串,例如MyClassIT, MyMethodITCase等。

其实就是我们的类命名不规范导致框架识别不出来。改类名后:

image.png

image.png

还有其它很多的注解,这里就不介绍了,篇幅有点长了。官方文档:org.junit.platform.suite.api (JUnit 5.10.0 API)

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论