JUnit 单元测试

1,394 阅读21分钟

单元测试(Unit Testing)是一种软件测试方法,用于验证软件应用中最小的可测试单元——通常是某个方法——是否按预期工作。单元测试的主要目的是确保代码的独立部分在隔离状态(不涉及它们与外部依赖或其他部分的交互)下能够正确执行。

JUnit 是由开发者 Erich Gamma 和 Kent Beck 创建的一个开源项目,允许开发者编写和执行代码的测试。它主要用于单元测试,即对代码的独立单元(通常是方法或类)进行隔离测试。

虽然现在 JUnit 版本已经迭代到第五个大版本了,但 JUnit 4 仍然在许多项目中使用。

⨳ JUnit 4 引入了基于注解的测试方法,扩展了断言功能,还引入了自定义测试运行器的概念,支持参数化测试,引入了 Assume 类,引入了规则(Rules)的概念...可以说JUnit 4 是 JUnit 框架的一个重要版本。

⨳ JUnit 5 的最大特点是它的模块化架构,分为 JUnit Platform、JUnit Jupiter 和 JUnit Vintage 三个主要部分:

  • JUnit Platform:作为基础平台,用于启动和发现测试,并且支持与各种工具和框架的集成。

  • JUnit Jupiter:提供了全新的测试 API 和注解,支持现代 Java 特性(如 Lambda 表达式)。

  • JUnit Vintage:这是一个兼容层,允许 JUnit 5 运行 JUnit 3 和 JUnit 4 的测试代码。这意味着你可以在一个项目中同时使用 JUnit 4 和 JUnit 5,而不需要将所有测试代码都迁移到新的框架。

要学就学最新的版本,我们直接在项目中引入 Junit5 依赖:

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

image.png

注解

核心注解基本上都位于 junit-jupiter-api 模块中的 org.junit.jupiter.api 包内,没事可以去那里面翻翻。

测试方法注解

测试方法注解注解用于标识测试方法或测试用例,并定义其行为。

@Test:标识一个标准的测试方法

@Test 注解是 JUnit 测试框架中最基本和最常用的注解,用于标识一个方法为测试方法。被 @Test 注解标识的方法会在运行测试时被 JUnit 执行,并且结果会被记录和报告。

import org.junit.jupiter.api.Test;

public class TestTest {
    @Test
    void testTest() {
       System.out.println("测试用例");
    }

}

@ParameterizedTest:标识一个参数化测试方法

参数化测试允许你用不同的参数集多次运行相同的测试方法,这在测试需要验证多个输入组合时非常有用。

@ParameterizedTest 需要与参数来源的注解配合使用,如 @ValueSource@EnumSource@MethodSource@CsvSource 等等。

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
void testParameterizedTest(String num){
    System.out.println("参数化测试用例:"+num);
}

输出结果如下:

参数化测试用例:one
参数化测试用例:two
参数化测试用例:three

参数来源的注解如下:

@ValueSource@EnumSource@MethodSource@CsvSource@CsvFileSource@ArgumentsSource
从简单值数组中提供参数从枚举类型中提供参数从提供者方法中提供参数从内联 CSV 数据中提供参数从 CSV 文件中提供参数从自定义参数提供者中提供参数

@RepeatedTest:标识一个重复执行的测试方法。

@RepeatedTest 可以指定测试方法应该被执行的次数,这在需要验证代码在多个运行中是否表现一致时非常有用。

@RepeatedTest(3)
void testRepeatedTest(){
    System.out.println("重复测试用例");
}

输出结果如下:

重复测试用例
重复测试用例
重复测试用例

@TestTemplate:标识一个测试模板,可以通过提供者多次执行

@TestTemplate 提供了一种机制来创建测试模板,这些模板可以在运行时由不同的测试实例填充。可以在运行时生成测试实例,这对于某些动态测试场景非常有用,比如根据配置文件或数据库状态执行测试。 @TestTemplate 通常与一个 TestTemplateInvocationContextProvider 配合使用,这个接口负责为每个测试实例提供上下文。

package com.cango.junit;

import org.junit.jupiter.api.extension.*;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.IntStream;
import java.util.stream.Stream;

// 用于为JUnit 5测试框架中的测试模板提供参数上下文。
public class MyInvocationContextProvider implements TestTemplateInvocationContextProvider {

    /**
     * 判断当前提供者是否支持测试模板。
     * @param extensionContext 测试框架的上下文信息
     * @return 总是返回true,表示支持测试模板
     */
    @Override
    public boolean supportsTestTemplate(ExtensionContext extensionContext) {
        return true;
    }

    /**
     * 提供测试模板的调用上下文列表。
     * @param extensionContext 测试框架的上下文信息
     * @return  包含10个TestTemplateInvocationContext实例的列表
     */
    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext extensionContext) {
        return IntStream.rangeClosed(1, 10)
                .mapToObj(item -> getTestTemplateInvocationContext());
    }

    /**
     * 生成测试模板的调用上下文。
     * @param
     * @return 包含一个ParameterResolver的TestTemplateInvocationContext对象
     */
    private TestTemplateInvocationContext getTestTemplateInvocationContext() {
        return new TestTemplateInvocationContext() {
            @Override
            public List<Extension> getAdditionalExtensions() {
                return Collections.singletonList(new ParameterResolver() {

                    /**
                     * 判断参数类型是否为Integer或String。
                     * @param parameterContext 参数上下文
                     * @return 如果参数类型为Integer或String,则返回true
                     */
                    @Override
                    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
                        Class<?> type = parameterContext.getParameter().getType();
                        return type.isAssignableFrom(Integer.class) || type.isAssignableFrom(String.class);
                    }

                    /**
                     * 解析参数。
                     * @param parameterContext 参数上下文
                     * @param extensionContext 测试框架的上下文信息
                     * @return 根据参数类型返回一个随机生成的Integer或UUID字符串
                     */
                    @Override
                    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
                        Class<?> type = parameterContext.getParameter().getType();

                        if (type.isAssignableFrom(Integer.class)) {
                            return (int) (Math.random() * 100);
                        } else if (type.isAssignableFrom(String.class)) {
                            return UUID.randomUUID().toString();
                        }
                        return null;
                    }
                });
            }
        };
    }

}

在测试执行时,JUnit 会使用 MyInvocationContextProvider 提供的上下文来“填充”测试模板,从而生成多个测试实例。

@TestTemplate
@ExtendWith(MyInvocationContextProvider.class)
void testTemplate(String input) {
    System.out.println("Testing with: " + input);
}

输出结果如下:

Testing with: dce5efc1-cf89-422c-9089-4a00d87e728b
Testing with: fd107ad8-62ad-4e3e-ac00-e9cdabfe5845
Testing with: 9395d5d5-600a-4244-a899-dfaef036aa07
Testing with: faa9034d-9917-4143-b546-2d731afe3ee0
Testing with: 65df34ab-1039-47db-80ab-fcc85171a62f
Testing with: 86b5fa0f-6286-4c78-ae28-2b1ad8cd0cb2
Testing with: 9bad1ae3-a3a9-4255-8bfe-244196c60eb6
Testing with: 7f135bf7-aa88-4dc5-8896-d8f2a72b5bcf
Testing with: dd3095a2-e245-412d-ac09-8c92af05b710
Testing with: 862e411d-ef0b-4142-ad37-375fded13683

当然,测试用例使用的参数类型是 String,所以参数被 UUID 所填充,如何是 Integer 类型,填充的就是随机数了,想要什么类型都可以进行配置,是不是比 @ParameterizedTest 厉害。

@TestFactory:标识一个工厂方法,用于动态生成测试用例。

@TestFactory是用于创建动态测试工厂的方法注解。与用 @Test 注解的常规测试方法不同,@TestFactory 注解的方法返回一个集合或流,这些集合或流会在运行时生成动态测试。

@TestFactory
Stream<DynamicTest> testTesFactory() {
    // Generates tests for the first 10 even integers.
    return IntStream.iterate(0, n -> n + 2).limit(10)
            .mapToObj(n -> dynamicTest("test" + n, () ->System.out.println("Testing with: " + n)));
}

输出结果如下:

Testing with: 0
Testing with: 2
Testing with: 4
Testing with: 6
Testing with: 8
Testing with: 10
Testing with: 12
Testing with: 14
Testing with: 16
Testing with: 18

动态测试是在运行时生成的测试,允许更灵活的参数化测试。每个动态测试都是通过 DynamicTest.dynamicTest() 方法创建的,该方法接收一个显示名称和一个可执行项(例如 Lambda 表达式或方法引用)。

⨳ ...

生命周期注解

这些注解用于配置在测试方法之前或之后执行的代码,帮助管理测试环境的初始化和清理。

@BeforeEach:在每个测试方法执行之前运行

这个注解标识的方法通常用来准备测试所需的环境或初始化一些状态,以确保每个测试方法在一致的前提下执行。适用场景如下:

  • 资源初始化:为每个测试用例设置初始条件,如创建必要的对象、清理旧数据或设置依赖的资源。

  • 重置状态:如果某些全局状态在每个测试方法中可能会改变,使用 @BeforeEach 可以确保在每次测试前将其重置为初始状态。

@AfterEach:在每个测试方法执行之后运行

@AfterEach 用于在每个测试方法执行之后运行一些代码。@AfterEach 注解的方法通常用于清理测试所产生的状态或释放资源,以确保测试之间没有相互影响。适用场景如下:

  • 资源清理:用于释放在测试方法中使用的资源,例如关闭文件、断开数据库连接、清理内存中的临时数据等。

  • 重置状态:如果某些全局状态在测试方法中被修改,可以在 @AfterEach 方法中将其重置为初始状态。

  • 日志记录:可以在每个测试方法结束后记录日志信息,便于调试和分析测试执行情况。

@AfterEach 通常与 @BeforeEach 配合使用,@BeforeEach 方法负责在每个测试方法之前初始化资源或状态,@AfterEach 方法则在测试方法执行之后清理这些资源或重置状态。

@BeforeAll:在所有测试方法执行之前运行,方法必须是静态的

@BeforeAll 用于在当前测试类中所有测试方法执行之前运行一些代码。与 @BeforeEach 不同,@BeforeAll 注解的方法只会在测试类开始执行时调用一次,通常用于执行一次性的全局初始化操作。适用场景如下:

  • 全局资源初始化:用于在所有测试方法运行之前初始化全局资源,如数据库连接、共享配置文件的加载、启动外部服务等。

  • 执行一次性的设置:当某些操作只需要执行一次时(例如在整个测试类的生命周期内),可以将这些操作放在 @BeforeAll 中。

@AfterAll:在所有测试方法执行之后运行,方法必须是静态的。

@AfterAll 用于在当前测试类中所有测试方法执行完毕之后运行一些代码。与 @AfterEach 不同,@AfterAll 注解的方法只会在所有测试方法完成后执行一次,通常用于清理那些在整个测试类中共享的全局资源或执行一些一次性的清理操作。适用场景如下:

  • 全局资源清理:用于清理在 @BeforeAll 中初始化的全局资源,如关闭数据库连接、停止外部服务、删除临时文件等。

  • 记录测试总结:可以在测试全部结束后记录汇总信息,例如测试报告的生成或日志记录。

  • 释放共享状态:适合在所有测试方法结束后一次性清理或重置共享状态或环境。

无论是 @BeforeEach@AfterEach 还是 @BeforeAll@AfterAll ,被他们注解的方法必须是 public void,并且不能接受任何参数,而且 @BeforeAll@AfterAll 还必须是静态方法(因为这些方法在整个测试类的上下文中只运行一次,而不是针对每个测试实例运行)。

条件执行注解

这些注解允许根据特定条件启用或禁用测试方法或测试类。

@Disabled:禁用测试方法或测试类

@Disabled 用于标记测试类或测试方法暂时不执行。这在想要跳过某些测试,或者暂时禁用特定测试类时非常有用。

可以将 @Disabled 注解放在一个测试方法上,以禁用该特定测试方法。也可以可以将 @Disabled 注解放在整个测试类上,以禁用该类中的所有测试方法。@Disabled 注解可以接受一个可选的 value 参数,用于说明禁用的原因。这有助于在测试报告中提供更多上下文信息。

@DisabledIf:根据布尔条件禁用测试方法或测试类

@Disabled 注解不同,@DisabledIf 提供了更强大的条件控制能力,允许你在测试执行时根据某些条件决定是否禁用测试。

@DisabledIf 需要传递一个返回 boolean 值的条件表达式或方法。 如果条件表达式的结果为 true,则测试方法或测试类会被禁用;如果结果为 false,测试方法或类将会执行。

@Test 
@DisabledIf("isFeatureXEnabled") 
void testConditional() { 
    // 这个测试会根据 isFeatureXEnabled() 的结果来决定是否执行 
} 
static boolean isFeatureXEnabled() { 
    // 假设这个方法检查某个配置或系统属性 
    return "true".equals(System.getProperty("featureX.enabled")); 、
}

@DisabledIf 作用相反的的 @EnabledIf

@DisabledOnOs:根据操作系统类型禁用测试方法或测试类

@DisabledOnOs 注解允许指定要禁用测试的操作系统。如 OS.WINDOWSOS.LINUXOS.MACOS.SOLARISOS.OTHER(用于指定其他操作系统)。

@DisabledOnOs 作用相反的的 @EnabledOnOs

@EnabledIfEnvironmentVariable:根据环境变量启用测试方法或测试类

@EnabledIfEnvironmentVariable 可以根据环境变量的值动态地启用测试方法或测试类。只有在指定的环境变量满足条件时,测试才会被执行。这个注解非常适合在不同的环境中运行测试时,根据环境变量的设置有选择地启用或禁用测试。

@Test 
@EnabledIfEnvironmentVariable(named = "USER", matches = "admin") 
@EnabledIfEnvironmentVariable(named = "ENV", matches = "PROD") void 
testOnlyForAdminInProd() { 
// 这个测试方法只有在 "USER" 环境变量为 "admin" 且 "ENV" 环境变量为 "PROD" 时才会执行 
}

@EnabledIfEnvironmentVariable 作用相反的的 @DisabledIfEnvironmentVariable

测试配置注解

这些注解用于配置测试类或测试方法的执行行为,例如名称、超时时间和执行顺序等。

@DisplayName:为测试类或测试方法设置自定义名称

@DisplayName 可以为测试类或测试方法指定一个自定义的显示名称。这有助于在测试报告中提供更具可读性或描述性的名称,而不是直接使用类名或方法名。这个注解对于提高测试报告的可读性、理解性特别有用。

@Tag:为测试类或测试方法设置标签,用于过滤测试执行

@Tag 可以为测试类或测试方法分配一个或多个标签。标签允许你对测试进行分类、组织,并且可以在运行测试时选择性地执行某些特定标签的测试。这对于大型测试套件的管理和运行非常有用,尤其是在需要根据上下文、环境或测试类型来区分和选择测试的场景下。

可以使用测试运行器或构建工具(如 Maven、Gradle)来选择性地运行具有特定标签的测试。例如,只运行被标记为 fast 的快速测试,或只运行被标记为 integration 的集成测试。

@Timeout:为测试方法设置超时时间

@Timeout 用于指定测试方法必须在规定的时间限制内完成。如果测试方法在规定的时间内没有完成,那么测试将失败,并抛出 org.junit.jupiter.api.extension.TimeoutException 异常。

@Test 
@Timeout(value = 500, unit = ChronoUnit.MILLIS) // 设置超时时间为 500 毫秒 
void testWithMillisecondTimeout() throws InterruptedException {
    // 模拟一个短时间运行的任务 
    Thread.sleep(400); // 这个测试将会通过,因为它在 500 毫秒内完成
}

@TestInstance:配置测试类的实例生命周期

@TestInstance 用于控制测试实例的创建策略。默认情况下,JUnit 为每个测试方法创建一个新的测试实例,这样可以确保测试之间的完全隔离。不过,有时候可能希望在同一测试类的不同测试方法之间共享状态,或者在整个测试类的生命周期内共享某些资源,这时可以使用 @TestInstance 注解。

@TestInstance 注解提供了两种生命周期策略:

  • PER_METHOD(默认值): JUnit 为每个测试方法创建一个新的测试实例。这保证了测试方法之间的隔离,每个测试方法都有一个全新的测试实例。
  • PER_CLASS: JUnit 为测试类创建一个单一的测试实例,并在这个实例上运行所有测试方法。这允许测试方法之间共享状态和资源。
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTests {

    @BeforeAll
    void setUp() {
        // 在所有测试方法之前运行一次
        System.out.println("Setting up resources");
    }

    @AfterAll
    void tearDown() {
        // 在所有测试方法之后运行一次
        System.out.println("Tearing down resources");
    }

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

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

在上面的例子中,setUp()tearDown() 方法在整个测试类的生命周期内只会执行一次,而不是每个测试方法之前或之后执行。所有的测试方法(test1test2)共享同一个测试实例,这意味着它们可以访问到 setUp() 中设置的状态。

@TestMethodOrder:指定测试方法的执行顺序

默认情况下,JUnit 5 的测试方法执行顺序是不确定的,但有时可能需要按照特定的顺序执行测试方法,以便确保测试的依赖关系或测试场景的正确性。

@TestMethodOrder 注解允许你指定测试方法的排序策略,通过设置不同的排序规则来控制测试方法的执行顺序。

  • MethodOrderer.OrderAnnotation: 按照 @Order 注解中指定的顺序执行测试方法。

  • MethodOrderer.Alphanumeric: 按照测试方法名称的字母数字顺序执行。

  • MethodOrderer.Random: 随机顺序执行测试方法。

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

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTests {

    @Test
    @Order(2)
    void testB() {
        System.out.println("Test B");
    }

    @Test
    @Order(1)
    void testA() {
        System.out.println("Test A");
    }

    @Test
    @Order(3)
    void testC() {
        System.out.println("Test C");
    }
}

@Nested:用于在测试类中嵌套定义内部测试类

使用 @Nested 注解,可以更具层次性和结构性地组织测试代码。这对于测试具有不同状态或上下文的组件特别有用,可以将相关的测试方法分组在一起,提高测试代码的可读性和维护性。

使用 @Nested 注解定义的测试类被称为嵌套测试类。它们可以包含自己的测试方法、生命周期方法(如 @BeforeEach@AfterEach)以及其他嵌套测试类。

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class CalculatorTests {

    @Nested
    class AdditionTests {
        @BeforeEach
        void setUp() {
            // 在 AdditionTests 类的每个测试方法之前运行
        }
        @Test
        void testAddition() {
            // 测试加法
        }
    }

    @Nested
    class SubtractionTests {
        @BeforeEach
        void setUp() {
            // 在 SubtractionTests 类的每个测试方法之前运行
        }
        @Test
        void testSubtraction() {
            // 测试减法
        }
    }
}

断言

断言是用于验证测试结果是否符合预期的静态方法。如果断言失败,测试将失败,并报告断言失败的具体信息。

值比较

assertEquals(expected, actual) : 验证两个值是否相等

assertEquals(5, 2 + 3, "2 + 3 should equal 5");

assertNotEquals(expected, actual) : 验证两个值是否不相等

assertNotEquals(5, 2 + 2, "2 + 2 should not equal 5");

assertArrayEquals(expectedArray, actualArray) : 验证两个数组是否相等

assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});

assertEquals(expected, actual, delta) : 验证两个浮点数是否在允许的误差范围内相等。

assertEquals(3.14, 3.1415, 0.01, "The values should be close to each other");

布尔条件

assertTrue(condition) : 验证条件是否为 true

assertTrue(5 > 2, "5 should be greater than 2");

assertFalse(condition) : 验证条件是否为 false

assertFalse(2 > 5, "2 should not be greater than 5");

对象状态

assertNull(actual) : 验证对象是否为 null

assertNull(null, "The object should be null");

assertNotNull(actual) : 验证对象是否不为 null

assertNotNull(new Object(), "The object should not be null");

异常验证

assertThrows(expectedType, executable) : 验证代码块是否抛出了预期的异常

assertThrows(IllegalArgumentException.class, () -> {
    throw new IllegalArgumentException("This is an expected exception");
});

assertDoesNotThrow(executable) : 验证代码块是否没有抛出异常

assertDoesNotThrow(() -> {
    // 代码块,期望不抛出异常
});

断言组合

assertAll(executables) : 执行多个断言,并在其中一个断言失败时,仍然执行其他断言

assertAll("group of assertions",
    () -> assertEquals(4, 2 + 2, "2 + 2 should equal 4"),
    () -> assertTrue("hello".startsWith("h"), "'hello' should start with 'h'")
);

扩展库

除了 JUnit 5 本身及其核心模块外,还有一些非常有用的扩展库,可以进一步增强 JUnit 的功能。

Hamcrest

Hamcrest 是一个用于编写测试断言的库,它提供了一种基于匹配器的语法,允许你创建更复杂、更可读的断言,提供了比 JUnit 自带的断言方法更多的灵活性。

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>2.2</version> 
    <scope>test</scope>
</dependency>

Hamcrest 的核心概念是“匹配器”。匹配器用于检查对象是否符合某种条件。Hamcrest 提供了一些内置的匹配器,你也可以自定义自己的匹配器。

is() : 检查实际值是否与期望值相等

复制代码
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

@Test
public void testIs() {
    assertThat("hello", is("hello"));
}

equalTo() : 检查实际值是否等于期望值

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

@Test
public void testEqualTo() {
    assertThat(42, equalTo(42));
}

containsString() : 检查字符串是否包含某个子字符串

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;

@Test
public void testContainsString() {
    assertThat("hello world", containsString("world"));
}

hasSize() : 检查集合的大小

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import java.util.List;
import java.util.ArrayList;

@Test
public void testHasSize() {
    List<String> list = new ArrayList<>();
    list.add("one");
    list.add("two");
    assertThat(list, hasSize(2));
}

notNullValue() : 检查对象是否不为 null

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;

@Test
public void testNotNullValue() {
    assertThat(new Object(), notNullValue());
}

⨳ ...

Mockito

Mockito 是一个非常流行的 Java 模拟(mocking)框架,用于单元测试中创建和配置模拟对象,以便在隔离环境中测试代码。它通过简单易用的 API,让开发者能够在不依赖真实对象的情况下,对方法调用进行模拟和验证。

  • 模拟对象(Mock Object) : 是一个用来替代真实对象的虚拟对象,用于在测试中模拟该对象的行为。

  • 间谍对象(Spy Object) : 是部分模拟的对象,它保留了真实对象的部分行为,并且可以对部分方法进行模拟。

  • 桩对象(Stub Object) : 是一个固定响应的模拟对象,通常用于提供预设的数据。

<!-- Mockito Core Dependency -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.5.0</version> 
    <scope>test</scope>
</dependency>

<!-- Mockito JUnit Jupiter Extension -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.5.0</version> 
    <scope>test</scope>
</dependency>

下面为一个简单的使用 Demo :

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

interface Service {
    String fetchData();
    String fetchData(String data);
}

public class MockitoTest {

    @Test
    public void testService() {
        // 创建模拟对象
        Service mockService = mock(Service.class);
        // 设置期望行为
        when(mockService.fetchData("123")).thenReturn("Mock Data");
        // 断言返回值
        String result = mockService.fetchData();
        assert "Mock Data".equals(result);

        // 验证 fetchData 方法被调用
        verify(mockService).fetchData();
        // 验证方法调用次数
        verify(mockService, times(1)).fetchData();


        // 模拟抛出异常
        when(mockService.fetchData()).thenThrow(new RuntimeException("Error"));
        assertThrows(RuntimeException.class, () -> {
            mockService.fetchData(); // 调用时抛出异常
        });

        // 使用参数匹配
        when(mockService.fetchData(anyString())).thenReturn("Mock Data with Parameter");

        result = mockService.fetchData("some data");
        assert "Mock Data with Parameter".equals(result);
        
    }
}

还有更多

JUnit Pioneer :扩展了 JUnit 5 的功能,提供了额外的生命周期方法、参数化测试的增强功能、条件执行等。它使 JUnit 5 更加灵活和强大,尤其在复杂的测试场景中。

EasyMock :和 Mockito 都是 Java 中常用的模拟(mocking)框架,只不过 Mockito 强调可读性和简洁性,遵循 "don't mock what you don't own" 的原则,即只模拟你自己编写的代码,而不是第三方库或系统资源。而 EasyMock 采用“录制-回放”模式,通过定义行为来设置期望值,然后重放这些行为进行测试。

Powermock:是一个能够扩展 Mockito 的框架,允许模拟静态方法、私有方法、构造函数等。

AssertJ :是一个流畅的断言库,提供了更丰富、更易读的断言 API,能够替代 JUnit 自带的断言方法。它支持各种数据类型的链式断言,并提供了更好的错误消息。

⨳ ...

总结

总之,JUnit 是 Java 中最流行的单元测试框架之一,它提供了一个简洁而强大的平台,用于编写和运行自动化测试。

JUnit 的核心就是那几个测试方法注解:

@Test 是最基本的测试注解,用于独立的、简单的测试场景。

@ParameterizedTest 可以使用各种参数源(如数组、CSV 文件、方法等)提供测试输入,适用于参数化测试场景。

@RepeatedTest 通过指定重复次数,测试方法会被多次执行。这在需要验证方法的幂等性或检查重复执行时的行为时非常有用。

@TestTemplate 适合高级场景,通过扩展机制重复执行同一个测试方法,但每次在不同的上下文中运行。

@TestFactory 通过工厂方法在运行时生成一组 DynamicTest,每个动态测试可以有不同的逻辑和输入,适用于需要在运行时动态计算测试的场景。

@Test是最简单的标识一个测试用例的注解,@ParameterizedTest 可以引入外部参数文件,@RepeatedTest 是可重复执行的测试用例注解,@TestTemplate 可动态生成参数对象,@TestFactory 可动态生成测试用例。