Junit5相关应用

Junit5的变化

Spring Boot 2.2.0版本开始引入Junit5作为单元测试默认库

整合SpringBoot之后加入@SpringBootTest可以使用Spring的相关功能如@Autowired

加入@Transactional 标注的 测试方法完成之后会自动回滚

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

Junit5 与之前 Junit框架有很大的不同,Junit 5主要由Junit Platform + Junit jupiter + Junit Vintage组成

  • Junit Platform 主要是支持JVM上面启动测试框架,不仅针对Junit自己的测试引擎,其他引擎也可以接入
  • Junit Jupiter Junit的核心库,测试引擎,在Junit Platform上运行
  • Junit Vintage 用于兼容老版本的Junit 如Junit4 3的用户

注意 在Spring Boot 2.4以上移除了对Vintage 的依赖,以为了使用老的Junit4的模式已经不适用了,如果需要兼容需要自行引入参考

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

常用Junit5的注解

  • @Test表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest表示方法是参数化测试,配合参数生成注解如@ValueSource
  • @RepeatedTest表示方法可重复执行,传入执行次数参数
  • @DisplayName为测试类或者测试方法设置展示名称
  • @BeforeEach表示在每个单元测试之前执行 类似Junit4 @Before
  • @AfterEach表示在每个单元测试之后执行 类似Junit4 @After
  • @BeforeAll表示在所有单元测试之前执行,方法要求是静态的 类似Junit4 @BeforeClass
  • @AfterAll表示在所有单元测试之后执行,方法要求是静态的 类似Junit4 @AfterClass
  • @Tag表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith为测试类或测试方法提供扩展类引用 类似Junit4 的@Runwith

常用示例 注意@Test 需要使用org.junit.jupiter.api包下的注解

package com.corn.junit;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;

import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * @author Jim Wu
 * @version 1.0
 * @function
 * @since 2021/2/3 9:46
 */
@DisplayName("单元测试示例")
public class Junit5Test {

    @BeforeAll
    static void beforeAll() {
        System.out.println("运行在所有单元测试之前...");
    }

    @AfterAll
    static void afterAll() {
        System.out.println(" 运行在所有单元测试之后 ...");
    }

    @BeforeEach
    public void beforeEach() {
        System.out.println(" 单元测试开始了... ");
    }

    @AfterEach
    public void afterEach() {
        System.out.println(" 单元测试结束了... ");
    }


    @DisplayName("简单测试")
    @Test
    public void simpleTest() {
        System.out.println("Simple test");
    }

    @DisplayName("重复测试")
    @RepeatedTest(5)
    public void repeatTest() {
        System.out.println("重复执行多次");
    }

    /**
     * ParameterizedTest需要配合参数源注解
     * <p>
     * {@link ValueSource}  可以通可选值中读取参数
     * {@link EnumSource} 从枚举中获取参数值
     * {@link CsvSource} 从csv中读取
     * {@link NullSource}  空参数
     * {@link MethodSource}  空集合参数
     * {@link EmptySource}  从方法中获取参数 要求方法必须是静态的 要求返回值必须是java.util.stream.Stream流
     */
    @DisplayName("参数化测试")
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4, 5})
    public void parameterizedTest(int i) {
        System.out.println(i);
    }

    @DisplayName("从方法中获取参数化测试")
    @ParameterizedTest
    @MethodSource(value = "getMethodParam")
    public void paramMethodTest(int i) {
        System.out.println(i);
    }

    static Stream<Integer> getMethodParam() {
        return Stream.of(11, 22, 33, 44, 55);
    }

    @DisplayName("被标注Disabled的方法")
    @Disabled
    @Test
    public void testDisable() {
        System.out.println("这个方法被标注了@Disabled不会执行");
    }

    @DisplayName("测试tag单元测试")
    @Tag("tag1")
    @Test
    public void testTag() {
        System.out.println("test tag");
    }

    @DisplayName("带超时的单元测试")
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    public void testTimeout() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(600);
    }

}

断言 Assertions

断言是UnitTest的核心功能,用于断言测试结果是否满足某一个条件的验证.org.junit.jupiter.api.Assertions中的静态方法 所有测试运行结束之后会生成一个测试报告

常用断言API

方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null

代码示例

package com.corn.junit;

import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;


/**
 * @author Jim Wu
 * @version 1.0
 * @function Junit5 的Assertion断言机制
 * @since 2021/2/3 11:02
 */

public class AssertionTest {

    /**
     * 断言Boolean
     */
    @Test
    public void testAssertionBoolean() {
        Assertions.assertTrue(true);
        Assertions.assertFalse(false);
    }

    /**
     * 断言相等Equal
     */
    @Test
    public void testAssertEquals() {
        Assertions.assertEquals(1, 1);
        Assertions.assertNotEquals("dd", "cc");
    }

    /**
     * 断言数组
     */
    @Test
    public void testAssertArray() {
        // 有序比较
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {1, 2, 3};
        Assertions.assertArrayEquals(arr1, arr2);

        // 无顺序比较
        List<Integer> list1 = Lists.newArrayList(1, 2, 3);
        List<Integer> list2 = Lists.newArrayList(3, 1, 2);
        // 注意Matcher参数list需要转成toArray 底层是循环出来一个一个判断
        assertThat(list1, containsInAnyOrder(list2.toArray()));

    }

    /**
     * 断言异常
     */
    @Test
    public void testAssertThrows() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            int i = 1 / 0;
        });
        Assertions.assertDoesNotThrow(() -> 1 / 1);
    }

    /**
     * 断言超时 如果超时断言失败
     */
    @Test
    public void testAssertTimeout() {
        Assertions.assertTimeout(Duration.ofMillis(300), () -> TimeUnit.MILLISECONDS.sleep(200));
    }

    /**
     * 断言对象是否相等
     */
    @Test
    public void testAssertSame() {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Assertions.assertSame(obj1, obj1);
        Assertions.assertNotSame(obj1, obj2);
    }

    /**
     * 断言 是否为空
     */
    @Test
    public void testNullValue() {
        Assertions.assertNull(null);
        Assertions.assertNotNull(1);
    }

    /**
     * 多个断言组合 都成功 断言成成功
     */
    @Test
    public void testAssertAll() {
        Assertions.assertAll("断言所有",
                () -> {
                    int i = 1 / 1;
                },
                () -> {
                    int i = 1 / 2;
                }
        );
    }

    @DisplayName("快速失败")
    @Test
    public void quickFail() {
        Assertions.fail("快速失败");
    }
}

前置条件Assumption

Assumption翻译过来就是假设, 在Junit用于判断前置条件,类似断言功能,不同之处在于如果不满足断言的话,这个测试会失败,而不满足前置条件则该测试方法就会终止执行.前置条件可以看做测试方法执行之前做的一些逻辑处理,如果前置条件不满足就不执行这个测试用例

package com.corn.junit;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

import static org.junit.jupiter.api.Assumptions.assumingThat;

/**
 * @author Jim Wu
 * @version 1.0
 * @function
 * @since 2021/2/3 14:34
 */

public class AssumptionTest {
    @Test
    public void testAssumptionBoolean() {
        Assumptions.assumeTrue(false);
        System.out.println(123);
    }

    @Test
    public void testAssert() {
        Assertions.assertTrue(false);
    }

    @Test
    public void testAssumptionThat() {
        assumingThat(true, () -> System.out.println("assumption_success"));
        assumingThat(false, () -> System.out.println("assumption_success"));
    }

}

image-20210203163929722

上图看到Assumption失败的方法会是禁止标志,表示该方法没有满足某一定条件被忽略ignore,该用例不会往下执行,而Assertion失败的会是一个×,表示测试用例失败

排序测试

@Order注解

package com.corn.junit;

import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;

/**
 * @author Jim Wu
 * @version 1.0
 * @function
 * @since 2021/2/3 15:36
 */

public class OrderTest {

    @Order(3)
    @Test
    public void testOrder3() {
        System.out.println("test3");
    }

    @Order(1)
    @Test
    public void testOrder1() {
        System.out.println("test1");
    }

    @Order(2)
    @Test
    public void testOrder2() {
        System.out.println("test2");
    }
}

条件测试

方法用途
@EnabledOnOs/@DisabledOnOs在某一个操作系统上执行或不执行
@EnabledOnJre/DisabledOnJre在某一个特定JDK环境下运行
@EnabledIfSystemProperty/@DisabledSystemProperty某一个JVM参数下运行
@EnabledIfEnvironmentVariable/@DisabledIfEnvironmentVariable某一个环境变量下运行在程序启动的之后可以添加到启动命令后
@EnabledIf / @DisabledIfSpring包下对Junit的扩展支持 支持SpEL表达式#{1==1},新版的Junit5也有了自己的支持到自定义静态方法
package com.corn.junit;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.*;
import org.springframework.test.context.junit.jupiter.DisabledIf;
import org.springframework.test.context.junit.jupiter.EnabledIf;

/**
 * @author Jim Wu
 * @version 1.0
 * @function
 * @since 2021/2/3 14:54
 */

public class ConditionTest {

    /**
     * 指定OS上运行
     */
    @EnabledOnOs(OS.WINDOWS)
    @Test
    public void testOS() {
        System.out.println("just running on windows os");
    }

    @DisabledOnOs(OS.WINDOWS)
    @Test
    public void testDisableOS() {
        System.out.println("nor running on windows");
    }

    @EnabledOnJre(JRE.JAVA_8)
    @Test
    public void testRunningOnJDK8() {
        System.out.println(" just running on JDK8");
    }

    @DisabledOnJre(JRE.JAVA_8)
    @Test
    public void testDisableOnJdk8() {
        System.out.println(" not running on JDK8");
    }

    /**
     * 系统变量 JVM的启动参数参数
     */
    @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
    @EnabledIfSystemProperty(named = "file.encoding", matches = "UTF-8")
    @Test
    public void testSystemProp() {
        System.out.println("just running on system prop");
    }

    /**
     * 环境变量 程序启动可以+
     */
    @EnabledIfEnvironmentVariable(named = "ooxx", matches = "true")
    @Test
    public void testEnvProp() {
        System.out.println("just running on environment  prop ooxx");
    }

    /**
     * 自定义方法计算出条件
     */
    @Test
    @EnabledIf("#{2*3==6}")
    void testEnableIf() {
        // ...
    }

    @Test
    @DisabledIf("#{1==1}")
    void testDisableIf() {
        // ...
    }

}

嵌套测试

@Nested

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制

package com.corn.junit;

import org.junit.jupiter.api.*;

import java.util.EmptyStackException;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author Jim Wu
 * @version 1.0
 * @function
 * @since 2021/2/3 15:37
 */
@DisplayName("嵌套测试")
public class NestTest {

    Stack<Object> stack;

    @DisplayName("初始化stack")
    @BeforeEach
    void iniStack() {
        stack = new Stack<>();
    }

    @Test
    public void testEmpty() {
        Assertions.assertTrue(stack.isEmpty());
    }

    @Nested
    @DisplayName("里面的Test")
    class InterTest {
        @Test
        public void testNotnull() {
            Assertions.assertNotNull(stack);
        }

        @Test
        public void testPopException() {
            Assertions.assertThrows(EmptyStackException.class, () -> stack.pop());
        }

        @Nested
        @DisplayName("最内层的Test")
        class InterestTest {
            @BeforeEach
            public void beforeEach() {
                stack.push(ThreadLocalRandom.current().nextInt());
            }

            @AfterEach
            public void afterEach() {
                stack.clear();
            }

            @Test
            public void testNotNull() {
                Assertions.assertNotNull(stack.pop());
            }

        }
    }
}

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试

参数化测试核心注解@ParameterizedTest 表明这个方法是一个参数化的测试方法

配合常用的参数生成注解

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
    /**
     * ParameterizedTest需要配合参数源注解
     * <p>
     * {@link ValueSource}  可以通可选值中读取参数
     * {@link EnumSource} 从枚举中获取参数值
     * {@link CsvSource} 从csv中读取
     * {@link NullSource}  空参数
     * {@link MethodSource}  空集合参数
     * {@link EmptySource}  从方法中获取参数 要求方法必须是静态的 要求返回值必须是java.util.stream.Stream流
     */
    @DisplayName("参数化测试")
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4, 5})
    public void parameterizedTest(int i) {
        System.out.println(i);
    }

    @DisplayName("从方法中获取参数化测试")
    @ParameterizedTest
    @MethodSource(value = "getMethodParam")
    public void paramMethodTest(int i) {
        System.out.println(i);
    }

    static Stream<Integer> getMethodParam() {
        return Stream.of(11, 22, 33, 44, 55);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值