JUnit5单元测试以及SpringBoot单元测试

示例代码:

本文档整理自教程:

JUnit 5 简介

JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

  • JUnit Platform : Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter : JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage : 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

JUnit 5 在运行时需要 Java 8 (或更高版本)。

快速开始

普通Maven项目

第一步:添加依赖

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.7.1</version>
    <scope>test</scope>
</dependency>

第二步:使用

import org.junit.jupiter.api.Test;
public class Demo {
    @Test
    public void demo() {
        System.out.println(123);
    }
}

SpringBoot环境

注: SpringBoot2.2.x 版本开始支持 JUnit5Spring Boot 2.2 Release Notes - JUnit 5

第一步:添加依赖

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

第二步:使用

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Junit5ApplicationTests {
    @Test
    void contextLoads() {
        System.out.println(123);
    }
}

添加 @SpringBootTest 注解后, JUnit 5 可以使用 SpringBoot 的功能,如 @Autowired 注解

详细说明

常用注解

@Test :表示方法是测试方法

JUnit4@Test 不同,他的职责非常单一不能声明任何属性,拓展的测试将会由 Jupiter 提供额外测试

@Test
void test() {
    System.out.println("test");
}

@Disabled :表示测试类或测试方法不执行

name 属性用于记录该测试方法停用的原因(非必须)

@Test
@Disabled("不用了")
void disabled() {
    System.out.println("disabled");
}

效果如下:

image

@DisplayName :为测试类或者测试方法设置展示名称

默认情况下,展示名称使用方法名或者类名

@DisplayName("测试常用注解")
class Annotations {
    @Test
    @DisplayName("自定义名称")
    void displayName() {
        System.out.println("displayName");
    }
}

效果如下:

image

@DisplayNameGeneration :声明测试类的自定义显示名称生成器。

JUnit Jupiter 支持自定义显示名称生成器,可以通过 @DisplayNameGeneration 注解进行配置。通过 @DisplayName 注解提供的值总是优先于由 DisplayNameGenerator 生成的显示名称。

可以通过实现 DisplayNameGenerator 来创建生成器。以下一些默认选项:

DisplayNameGenerator 说明
Standard 匹配JUnit Jupiter 5.0发布以来的标准显示名生成行为。
Simple 移除不带参数的方法的末尾括号。
ReplaceUnderscores 将下划线替换为空格。
IndicativeSentences 通过连接测试和外围类的名称来生成完整的句子。

@RepeatedTest :表示方法可重复执行

value 属性指定当前测试方法执行的次数

// @RepeatedTest(value = 3) // 不设置name时,value可以省略
@RepeatedTest(3)
void repeatedTest1() {
    System.out.println("repeatedTest1");
}

name 属性自定义显示内容

name 支持如下占位符,默认使用 repetition {currentRepetition} of {totalRepetitions}

  • {displayName} :方法名称
  • {totalRepetitions} :总次数
  • {currentRepetition} :当前第几次
@RepeatedTest(value = 3, name = "{displayName}方法:总计{totalRepetitions}次,当前第{currentRepetition}次")
void repeatedTest2() {
    System.out.println("repeatedTest2");
}

效果如下:

image

@BeforeEach / @AfterEach / @BeforeAll / @AfterAll :测试前与测试后

  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
@BeforeEach
void beforeEach() {
    System.out.println("beforeEach");
}
@AfterEach
void afterEach() {
    System.out.println("afterEach");
}
@BeforeAll
static void beforeAll() {
    System.out.println("beforeAll");
}
@AfterAll
static void afterAll() {
    System.out.println("afterAll");
}

效果如下:

image

@Timeout :表示测试方法运行如果超过了指定时间将会抛出异常

value 指定数值; unit 指定单位,默认 TimeUnit.SECONDS

@Test
@Timeout(value = 2000, unit = TimeUnit.MILLISECONDS)
void timeout() {
    System.out.println("timeout");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

效果如下:

image

根据运行效果得出结论:即使运行异常, @AfterEach@AfterAll 依旧执行

@Tag :用于在类或方法级别上声明用于过滤测试的标签;

通过 @Tag 注解对测试类和方法进行标记。类似于TestNG中的测试组或 JUnit 4 中的类别。

@Test
@Tag("taxes")
void testingTaxCalculation() {
    System.out.println("123");
}

@TestMethodOrder / @Order :用于为带注解的测试类配置测试方法的执行顺序

默认情况下,测试方法将使用确定性的算法进行排序,但故意不明显。这确保了测试套件的后续运行以相同的顺序执行测试方法,从而允许可重复的构建。虽然真正的单元测试通常不应该依赖于它们执行的顺序,但有时需要强制执行特定的测试方法执行顺序

要控制测试方法执行的顺序,请使用 @TestMethodOrder 注解的测试类或测试接口,并指定所需的 MethodOrderer 实现。也可以实现自定义 MethodOrderer ,或者使用以下内置的 MethodOrderer 实现之一。

  • DisplayName :根据测试方法的显示名称按字母和数字排序
  • MethodName :根据方法名和正式参数列表按字母数字顺序对测试方法进行排序。
  • OrderAnnotation :根据 @Order 注解指定的值对测试方法进行数字排序。
  • Random :订单测试方法伪随机,并支持自定义种子的配置。
  • Alphanumeric :根据测试方法的名称和形式参数列表按字母-数字排序。(不推荐使用会,在6.0中被移除)

示例:

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)
    void nullValues() {
        System.out.println("nullValues");
    }
    @Test
    @Order(2)
    void emptyValues() {
        System.out.println("emptyValues");
    }
    @Test
    @Order(3)
    void validValues() {
        System.out.println("validValues");
    }
}

效果如下:

image

断言(Assertions)

断言(assertions) 是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。

简单断言

用来对单个值进行简单的验证。方法如下:

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

示例:

@Test
public void simple() {
    // 判断两个对象或两个原始类型是否相等
    assertEquals(3, 1 + 2);
    // 判断两个对象或两个原始类型是否不相等
    assertNotEquals(3, 1 + 1);
    // 判断两个对象引用是否指向同一个对象
    Object obj = new Object();
    assertSame(obj, obj);
    // 判断两个对象引用是否指向不同的对象
    assertNotSame(obj, new Object());
    // 判断给定的布尔值是否为 true
    assertTrue(1 < 2);
    // 判断给定的布尔值是否为 false
    assertFalse(1 > 2);
    // 判断给定的对象引用是否为 null
    assertNull(null);
    // 判断给定的对象引用是否不为 null
    assertNotNull(obj);
}

以上测试方法均可添加一个 String message 参数,当断言失败时,显示当前 message

示例:

@Test
public void simpleWithMessage() {
    // 断言失败时指定消息
    assertEquals(3, 2 + 2, "自定义断言失败消息");
}

运行:

image

  • Expected:期望值
  • Actual:实际值

数组 / 迭代器 / 字符串 列表断言

@Test
public void array() {
    // 判断数组是否相同
    assertArrayEquals(new int[] {1, 2}, new int[] {1, 2});
    // 判断Iterable是否相同
    assertIterableEquals(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3));
    // 判断字符串列表
    List<String> list1 = Arrays.asList("1", "2", "3");
    List<String> list2 = Arrays.asList("1", "2", "3");
    assertLinesMatch(list1, list2);
}

组合断言

@Test
public void all() {
    // 组合断言(可任意个Executable)
    assertAll(() -> assertEquals(2, 1 + 1), () -> assertTrue(1 > 0));
}

异常断言

@Test
public void exceptionTest() {
    // 不抛出异常
    assertDoesNotThrow(() -> System.out.println(1 % 1), "抛出异常了");
    // 抛出指定异常
    assertThrows(ArithmeticException.class, () -> System.out.println(1 % 0));
}

超时断言

@Test
public void timeout() {
    // 如果测试方法时间超过指定时间将会失败(需要等方法执行结束再判断)
    assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(2000));
    // 如果测试方法时间超过指定时间将会失败(超过指定时间时立即失败,不等待方法执行结束)
    assertTimeoutPreemptively(Duration.ofMillis(1000), () -> Thread.sleep(2000));
}

快速失败

@Test
public void fastFail() {
    // 快速失败(直接调用,使当前测试方法失败)
    fail();
}

假设(Assumptions)

假设 可以看成是 断言 执行的前提,当该 假设 不满足时,就停止执行 断言 等后续方法。所有 假设 方法都是 org.junit.jupiter.api.Assumptions 类中的静态方法。

断言假设 的区别

  • 断言:不满足的断言会使得测试方法失败
  • 假设:不满足的假设条件会使得测试方法的执行终止

示例:

// 环境
private final String environment = "DEV";
@Test
void trueOrFalse() {
    // 判断当前环境,应当是 DEV
    assumeTrue("DEV".equals(environment));
    // 判断当前环境,不能是 PRO
    assumeFalse("PRO".equals(environment));
}
@Test
void trueOrFalseWithMessage() {
    // 判断当前环境,应当是 DEV
    assumeTrue("DEV".equals(environment), "当前不是DEV环境");
    // 判断当前环境,不能是 PRO
    assumeFalse("DEV".equals(environment), "当前是PRO环境");
}
@Test
void that() {
    // 指定环境下执行的断言
    assumingThat("DEV".equals(environment), () -> {
        System.out.println("当前是DEV环境,可以执行如下断言");
        assertEquals(2, 4 - 2);
    });
    // 任何环境下都执行的断言
    assertEquals(42, 6 * 7);
    System.out.println("执行结束");
}
  • assumeTrueassumFalse 确保给定的条件为 true 或 ``false` ,不满足条件会使得测试执行终止。
  • assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。
    • 条件满足时, Executable 对象才会被执行;
    • 条件不满足时,跳过当前 Executable ,执行后续测试。

有条件的测试执行

JUnit Jupiter 中的 ExecutionCondition 扩展 API 允许开发人员基于特定条件以编程方式启用或禁用容器或测试。

操作系统条件

通过 @EnabledOnOs / @DisabledOnOs 注解在特定操作系统上启用或禁用容器或测试 。

示例:

import static org.junit.jupiter.api.condition.OS.LINUX;
import static org.junit.jupiter.api.condition.OS.MAC;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
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.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
class ConditionOS {
    @Test
    @EnabledOnOs(MAC)
    void onlyOnMacOs() {
        // ...
    }
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Test
    @EnabledOnOs(MAC)
    @interface TestOnMac {}
    @TestOnMac
    void testOnMac() {
        // ...
    }
    @Test
    @EnabledOnOs({LINUX, MAC})
    void onLinuxOrMac() {
        // ...
    }
    @Test
    @DisabledOnOs(WINDOWS)
    void notOnWindows() {
        // ...
    }
}

Java运行时环境条件

可以通过 @EnabledOnJre@DisabledOnJre 注解在特定版本的 JRE 上启用或禁用,或者通过 @EnabledForJreRange@DisabledForJreRange 注解在特定范围的 JRE 版本上启用或禁用。范围默认为 JRE.java_8 作为下边界(min)和 JRE.OTHER 作为较高的边界(max),允许使用半开放范围。

import static org.junit.jupiter.api.condition.JRE.JAVA_10;
import static org.junit.jupiter.api.condition.JRE.JAVA_11;
import static org.junit.jupiter.api.condition.JRE.JAVA_8;
import static org.junit.jupiter.api.condition.JRE.JAVA_9;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.EnabledOnJre;
class ConditionJar {
    @Test
    @EnabledOnJre(JAVA_8)
    void onlyOnJava8() {
        // ...
    }
    @Test
    @EnabledOnJre({JAVA_9, JAVA_10})
    void onJava9Or10() {
        // ...
    }
    @Test
    @EnabledForJreRange(min = JAVA_9, max = JAVA_11)
    void fromJava9to11() {
        // ...
    }
    @Test
    @EnabledForJreRange(min = JAVA_9)
    void fromJava9toCurrentJavaFeatureNumber() {
        // ...
    }
    @Test
    @EnabledForJreRange(max = JAVA_11)
    void fromJava8To11() {
        // ...
    }
    @Test
    @DisabledOnJre(JAVA_9)
    void notOnJava9() {
        // ...
    }
    @Test
    @DisabledForJreRange(min = JAVA_9, max = JAVA_11)
    void notFromJava9to11() {
        // ...
    }
    @Test
    @DisabledForJreRange(min = JAVA_9)
    void notFromJava9toCurrentJavaFeatureNumber() {
        // ...
    }
    @Test
    @DisabledForJreRange(max = JAVA_11)
    void notFromJava8to11() {
        // ...
    }
}

系统属性条件

通过 @EnabledIfSystemProperty@DisabledIfSystemProperty 注解根据命名的JVM系统属性的值来启用或禁用。 matches 属性提供的值将被解释为一个正则表达式。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
class ConditionSystem {
    @Test
    @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
    void onlyOn64BitArchitectures() {
        // ...
    }
    @Test
    @DisabledIfSystemProperty(named = "ci-server", matches = "true")
    void notOnCiServer() {
        // ...
    }
}

JUnit Jupiter 5.6 开始, @EnabledIfSystemProperty@DisabledIfSystemProperty 是可重复的注释。因此,这些注释可以在测试接口、测试类或测试方法上声明多次。具体来说,如果这些注释在给定元素上直接存在、间接存在或元存在,就可以找到它们。

环境变量条件

通过 @EnabledIfEnvironmentVariable@DisabledIfEnvironmentVariable 注解,根据底层操作系统中命名的环境变量的值来启用或禁用。 matches 属性提供的值将被解释为一个正则表达式。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
class ConditionEnvironment {
    @Test
    @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
    void onlyOnStagingServer() {
        // ...
    }
    @Test
    @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
    void notOnDeveloperWorkstation() {
        // ...
    }
}

JUnit Jupiter 5.6 开始, @EnabledIfEnvironmentVariable@DisabledIfEnvironmentVariable 是可重复的注释。因此,这些注释可以在测试接口、测试类或测试方法上声明多次。具体来说,如果这些注释在给定元素上直接存在、间接存在或元存在,就可以找到它们。

自定义条件

通过 @EnabledIf@DisabledIf 注解基于方法的布尔返回来启用或禁用。方法通过注释的名称提供给注释,如果位于测试类之外,则通过注释的完全限定名称提供方法。如果需要,条件方法可以接受 ExtensionContext 类型的单个参数。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
class ConditionCustom {
    @Test
    @EnabledIf("customCondition")
    void enabled() {
        // ...
    }
    @Test
    @DisabledIf("customCondition")
    void disabled() {
        // ...
    }
    boolean customCondition() {
        return true;
    }
}

@EnabledIf@DisabledIf 在类级别使用时,条件方法必须始终是 静态 的。位于外部类中的条件方法也必须是 静态 的。在任何其他情况下,可以同时使用静态方法或实例方法。

嵌套测试

官方示例:

public class TestingAStackDemo {
    Stack<Object> stack;
    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }
    @Nested
    @DisplayName("when new")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }
        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }
        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }
        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }
        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {
            String anElement = "an element";
            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }
            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }
            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }
            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

运行结果:

image

在此示例中,通过为设置代码定义分层生命周期方法,内部测试中使用了外部测试的前提条件,例如: createNewStack() 。在执行内部测试之前先运行外部测试的安装代码这一事实使您能够独立运行所有测试。您甚至可以单独运行内部测试,而无需外部测试,因为始终会执行外部测试中的设置代码。

参数化测试

@ParameterizedTest :使用参数化测试注解

通过参数化测试,可以使用不同的参数多次运行测试。它的声明与常规 @Test 方法一样,但是使用 @ParameterizedTest 批注。另外,必须声明至少一个 参数源 ,该源将为每次调用提供参数,然后在方法中使用这些参数。

示例:演示参数化测试,该测试使用 @ValueSource 注解将 String 数组指定为参数源。

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void valueSource(int argument) {
    assertTrue(argument > 0 && argument < 4);
}

具体示例来源见下文

@ValueSource :简单的数据源

支持以下类型的文字值:

  • short
  • byte
  • int
  • long
  • float
  • double
  • char
  • boolean
  • java.lang.String
  • java.lang.Class

示例见上文

null和空数据源

为了检查极端情况并验证软件的正确行为,需要为参数化测试提供null和空值。

  • @NullSource :为带null注释的@ParameterizedTest 方法提供单个参数。 @NullSource 不能用于具有原始类型的参数。
  • @EmptySource :提供了一个单一的空参数,注解 @ParameterizedTest 方法为以下类型的参数:
    • java.lang.String
    • java.util.List
    • java.util.Set
    • java.util.Map
    • 原始数组(例如,int[],char[][]等)
    • 对象数组(例如,String[],Integer[][]等等)。
  • @NullAndEmptySource :组合了 @NullSource@EmptySource 的功能的组合注解。

示例:

@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = {" ", "   ", "\t", "\n"})
void nullEmptyAndBlankStrings1(String text) {
    assertTrue(text == null || text.trim().isEmpty());
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "   ", "\t", "\n"})
void nullEmptyAndBlankStrings2(String text) {
    assertTrue(text == null || text.trim().isEmpty());
}

@EnumSource :枚举数据源

提供了使用Enum常量的便捷方法。

@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithEnumSource(TemporalUnit unit) {
    assertNotNull(unit);
}

@MethodSource :方法返回值数据源

使用测试类或外部类的一个或多个工厂方法的返回值作为数据源

如果只需要一个参数,则可以返回一个 Stream 参数类型的实例。

示例:

@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
    assertNotNull(argument);
}
static Stream<String> stringProvider() {
    return Stream.of("apple", "banana");
}

如果未设置 @MethodSource 注解的 value 属性,则 JUnit Jupiter 将按照约定搜索与当前 @ParameterizedTest 方法同名的工厂方法。

示例:

@ParameterizedTest
@MethodSource
void testWithDefaultLocalMethodSource(String argument) {
    assertNotNull(argument);
}
static Stream<String> testWithDefaultLocalMethodSource() {
    return Stream.of("apple", "banana");
}

支持其他基本类型(DoubleStream,IntStream和LongStream)的流。

示例:

@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
    assertNotEquals(9, argument);
}
static IntStream range() {
    return IntStream.range(0, 20).skip(10);
}

如果参数化测试方法声明了多个参数,则需要返回一个如下所示的Arguments实例对象或集合、流或数组,( @MethodSource 有关支持的返回类型的更多详细信息,请参阅 Javadoc )。

示例:

@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List<String> list) {
    assertEquals(5, str.length());
    assertTrue(num >= 1 && num <= 2);
    assertEquals(2, list.size());
}
static Stream<Arguments> stringIntAndListProvider() {
    return Stream.of(arguments("apple", 1, Arrays.asList("a", "b")),
        arguments("lemon", 2, Arrays.asList("x", "y")));
}

可以通过提供完全限定的方法名来引用外部静态工厂方法

示例:

package com.maxqiu.demo;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
class Parameterized {
    @ParameterizedTest
    @MethodSource("com.maxqiu.demo.StringsProviders#tinyStrings")
    void testWithExternalMethodSource(String tinyString) {
        System.out.println(tinyString);
        // test with tiny string
    }
}
class StringsProviders {
    static Stream<String> tinyStrings() {
        return Stream.of(".", "oo", "OOO");
    }
}

@CsvSource :CSV格式数据源

将参数列表表示为以逗号分隔的值

示例:

@ParameterizedTest
@CsvSource({"apple,1", "banana,2", "'lemon,lime',0xF1"})
void testWithCsvSource(String fruit, int rank) {
    assertNotNull(fruit);
    assertNotEquals(0, rank);
}

默认的分隔符是逗号” , “,可以通过 delimiter 属性来使用另一个字符。

@CsvSource 使用单引号 ' 作为其引号字符。除非设置了属性,否则带引号的值 '' 为空,除非设置了 emptyValue 属性。相反,将一个完全为空的值解释为 null 。通过指定一个或多个nullValues,可以将自定义值解释为null引用(请参见下表中的NIL示例)。如果一个的目标类型是一个基本类型,则抛出ArgumentConversionException

不带引号的空值将始终转换为空引用,而不管通过nullValues属性配置的任何自定义值。

输入示例 结果参数列表
@CsvSource({ “apple, banana” }) “apple”, “banana”
@CsvSource({ “apple, ‘lemon, lime’” }) “apple”, “lemon, lime”
@CsvSource({ “apple, ‘’” }) “apple”, “”
@CsvSource({ “apple, “ }) “apple”, null
@CsvSource(value = { “apple, banana, NIL” }, nullValues = “NIL”) “apple”, “banana”, null

@CsvFileSource :CSV文件数据源

使用来自类路径或本地文件系统的CSV文件。CSV文件中的每一行都会导致对参数化测试的一次调用。

示例:

@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromFile(String country, int reference) {
    assertNotNull(country);
    assertNotEquals(0, reference);
}

CSV文件内容:

Country, reference
Sweden, 1
Poland, 2
"United States of America", 3

默认的分隔符是逗号” , “,可以通过 delimiter 属性来使用另一个字符。任何以 # 符号开头的行都将被解释为注释,并且将被忽略。

@CsvSource 中使用的语法不同, @CsvFileSource 使用双引号 "" 作为引号字符。除非设置了属性,否则带引号的值 '' 为空,除非设置了 emptyValue 属性。相反,将一个完全为空的值解释为 null 。通过指定一个或多个nullValues,可以将自定义值解释为null引用(请参见下表中的NIL示例)。如果一个的目标类型是一个基本类型,则抛出ArgumentConversionException

不带引号的空值将始终转换为空引用,而不管通过nullValues属性配置的任何自定义值。

@ArgumentsSource

定自定义的,可重用的ArgumentsProvider。请注意,ArgumentsProvider必须将的实现声明为顶级类或static嵌套类。

示例:

class Parameterized {
    @ParameterizedTest
    @ArgumentsSource(MyArgumentsProvider.class)
    void testWithArgumentsSource(String argument) {
        assertNotNull(argument);
    }
}
class MyArgumentsProvider implements ArgumentsProvider {
    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of("apple", "banana").map(Arguments::of);
    }
}

补充说明

如何在打包时跳过测试

方式一:命令行

mvn clean package -DskipTests
# 或者
mvn clean package -Dmaven.test.skip=true

方式二:修改pom

<build>
    <plugins>
        <!-- maven 打包时跳过测试 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skip>true</skip>
            </configuration>
        </plugin>
    </plugins>
</build>

方案三: IntelliJ IDEA 开发工具配置

点击图中的按钮,之后 test 就会变成灰色,即跳过测试

image

JUnit4迁移JUnit5

在将 JUnit 4 测试迁移到 JUnit5JUnit Jupiter )时,应该注意以下事项。

  • 注解在 org.junit.jupiter.api 包中。
  • 断言在 org.junit.jupiter.api.Assertions 包中。
  • 假设在 org.junit.jupiter.api.Assumptions 包中。
  • @Before@After 不再存在,使用 @BeforeEach@AfterEach 代替。
  • @BeforeClass@AfterClass 不再存在,用 @BeforeAll@AfterAll 代替。
  • @Ignore 不再存在,使用 @Disabled 或其他内置执行条件之一代替
  • @Category 不再存在,使用 @Tag 代替。
  • @RunWith 不再存在,使用 @ExtendWith 代替。
  • @Rule@ClassRule 不再存在,被 @ExtendWith@RegisterExtension 取代

原文:JUnit5单元测试以及SpringBoot单元测试