Junit5使用教程(4) --扩展模型

第四部分:JUnit 5 扩展模型

1. JUnit 5 扩展机制概述

JUnit 5 的扩展机制(Extension Model)是其最强大的特性之一,允许开发者通过自定义逻辑干预测试生命周期、增强测试功能,或与第三方框架无缝集成。它通过模块化扩展取代了 JUnit 4 中基于 @RunWith 的单继承模型,提供了更高的灵活性和可维护性。


一、扩展机制的核心思想

  1. 非侵入式设计
    无需继承特定基类,通过实现接口或使用注解即可插入自定义逻辑。
  2. 生命周期钩子
    允许在测试的各个阶段(如初始化、执行、清理)插入自定义代码。
  3. 组合式扩展
    支持同时使用多个扩展,通过 @ExtendWith 注解组合功能。

二、扩展的核心组件

组件说明
扩展接口定义扩展行为的接口(如 BeforeEachCallbackParameterResolver)。
扩展实现类实现扩展接口的具体逻辑(如 MockitoExtension)。
扩展注册通过 @ExtendWith 注解或配置文件(ServiceLoader)注册扩展。

三、常用扩展接口

JUnit 5 提供了多种扩展接口,覆盖测试生命周期的不同阶段:

接口名称作用阶段典型场景
BeforeAllCallback所有测试前执行全局资源初始化
BeforeEachCallback每个测试方法前执行测试数据准备
AfterEachCallback每个测试方法后执行清理临时资源
AfterAllCallback所有测试后执行全局资源释放
TestExecutionCondition动态决定是否执行测试条件测试(如环境变量检查)
ParameterResolver解析测试方法参数依赖注入(如 Mock 对象、数据库连接)
TestWatcher监控测试结果(成功、失败、跳过)日志记录、报告生成
TestInstanceFactory控制测试实例的创建方式单例模式测试、依赖注入容器集成

四、扩展的注册方式

  1. 类级注册
    通过 @ExtendWith 注解将扩展附加到测试类:

    @ExtendWith(MockitoExtension.class)
    class MyTest {
        // 测试方法
    }
    
  2. 方法级注册
    针对单个测试方法注册扩展:

    class MyTest {
        @Test
        @ExtendWith(CustomExtension.class)
        void myTest() { ... }
    }
    
  3. 全局注册
    通过 META-INF/services 配置文件自动加载扩展,无需显式注解:

    # 文件路径:src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
    com.example.MyGlobalExtension
    

五、扩展的执行顺序

  1. 同类型扩展的顺序
    JUnit 5 默认不保证相同类型扩展的执行顺序,但可通过 @Order 注解指定优先级:

    @Order(1)  // 值越小优先级越高
    public class ExtensionA implements BeforeEachCallback { ... }
    
  2. 扩展与生命周期注解的交互

    • @BeforeAll 前:BeforeAllCallback@BeforeAll
    • @BeforeEach 前:BeforeEachCallback@BeforeEach
    • @AfterEach 后:@AfterEachAfterEachCallback
    • @AfterAll 后:@AfterAllAfterAllCallback

六、自定义扩展的开发步骤

  1. 选择扩展接口
    根据需求实现对应接口(如 ParameterResolver 用于依赖注入)。
  2. 实现接口方法
    编写核心逻辑(如解析参数、监控测试状态)。
  3. 注册扩展
    通过 @ExtendWith 或全局配置启用扩展。

示例:自定义参数解析器

public class RandomNumberResolver implements ParameterResolver {
    @Override
    public boolean supportsParameter(ParameterContext paramCtx, ExtensionContext extCtx) {
        return paramCtx.getParameter().getType() == int.class;
    }

    @Override
    public Object resolveParameter(ParameterContext paramCtx, ExtensionContext extCtx) {
        return new Random().nextInt(100);
    }
}

// 使用扩展
@ExtendWith(RandomNumberResolver.class)
class MyTest {
    @Test
    void testWithRandomNumber(int number) {
        Assertions.assertTrue(number >= 0 && number < 100);
    }
}

七、扩展机制的应用场景

  1. 依赖注入
    集成 Spring、Guice 等框架(如 SpringExtension)。
  2. Mock 对象管理
    自动创建和注入 Mockito 的 @Mock 对象(如 MockitoExtension)。
  3. 条件测试
    根据环境变量、配置或数据库状态动态跳过测试。
  4. 资源管理
    自动创建/清理临时文件、数据库连接或网络资源。
  5. 性能监控
    记录测试执行时间、内存占用等指标。

八、扩展机制的优点

  • 模块化:将通用逻辑封装为可重用的扩展。
  • 灵活性:支持按需组合功能,避免代码冗余。
  • 生态兼容:无缝集成主流框架(如 Spring、Mockito、Testcontainers)。

九、注意事项

  1. 避免过度扩展
    优先使用现有扩展(如官方或社区维护的库)。
  2. 保持扩展轻量
    避免在扩展中执行耗时操作(如阻塞 I/O)。
  3. 明确扩展职责
    单一扩展应专注于一个功能(如依赖注入或资源管理)。

通过扩展机制,开发者可以突破 JUnit 5 的默认行为,构建高度定制化的测试解决方案,为复杂项目提供强大的测试支持。

2. JUnit 5 常用扩展使用场景与示例

JUnit 5 的扩展机制为测试提供了强大的灵活性。以下是常用扩展的使用场景及具体示例:


一、依赖注入扩展:MockitoExtension

场景:在单元测试中模拟外部依赖(如数据库、API 调用)。
功能:自动创建 Mock 对象并注入到被测类。
示例

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;  // 模拟依赖

    @InjectMocks
    private UserService userService;        // 自动注入 Mock 对象

    @Test
    void testFindUserById() {
        when(userRepository.findById(1L)).thenReturn(new User(1L, "Alice"));
        User user = userService.findUserById(1L);
        Assertions.assertEquals("Alice", user.getName());
    }
}

二、Spring 集成扩展:SpringExtension

场景:在集成测试中加载 Spring 上下文并注入 Bean。
功能:支持 Spring 的依赖注入和事务管理。
示例

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;  // 注入 Spring Bean

    @Test
    void testSaveUser() {
        User user = new User("Bob");
        userService.save(user);
        Assertions.assertNotNull(user.getId());
    }
}

三、临时目录扩展:TempDirectory

场景:测试中需要创建临时文件或目录。
功能:自动创建和清理临时资源。
示例

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;

class TempDirTest {

    @Test
    void testTempFile(@TempDir Path tempDir) throws IOException {
        Path file = tempDir.resolve("test.txt");
        Files.write(file, "Hello".getBytes());
        Assertions.assertTrue(Files.exists(file));
    }
}

四、超时控制扩展:Timeout

场景:限制测试方法的执行时间,防止无限阻塞。
功能:强制中断超时的测试。
示例

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeUnit;

class TimeoutTest {

    @Test
    @Timeout(value = 1, unit = TimeUnit.SECONDS)
    void testNotExceedTimeout() throws InterruptedException {
        Thread.sleep(500);  // 测试通过
    }

    @Test
    @Timeout(value = 1, unit = TimeUnit.SECONDS)
    void testExceedTimeout() throws InterruptedException {
        Thread.sleep(1500); // 测试失败
    }
}

总结

扩展类型核心场景关键接口/注解
依赖注入单元测试中的 Mock 对象管理MockitoExtension@Mock
Spring 集成Spring Bean 的集成测试SpringExtension@Autowired
临时资源管理创建和清理临时文件/目录@TempDir
超时控制防止测试无限阻塞@Timeout

通过合理使用这些扩展,可以显著提升测试代码的灵活性和可维护性,覆盖复杂项目的多样化需求。


3. JUnit 5 自定义扩展

JUnit 5 的扩展机制允许开发者通过实现特定接口(如 BeforeEachCallbackParameterResolver)自定义测试行为。本章将详细介绍如何开发自定义扩展,并通过具体示例展示其应用。


一、自定义扩展的核心步骤

  1. 选择扩展接口
    根据需求选择 JUnit 5 提供的扩展接口(如 BeforeEachCallbackParameterResolver)。
  2. 实现接口方法
    编写扩展逻辑(如初始化资源、注入参数)。
  3. 注册扩展
    通过 @ExtendWith 注解或全局配置启用扩展。

二、常用扩展接口

接口名称作用阶段典型场景
BeforeAllCallback所有测试前执行全局资源初始化
BeforeEachCallback每个测试方法前执行测试数据准备
AfterEachCallback每个测试方法后执行清理临时资源
AfterAllCallback所有测试后执行全局资源释放
TestExecutionCondition动态决定是否执行测试条件测试(如环境变量检查)
ParameterResolver解析测试方法参数依赖注入(如 Mock 对象、数据库连接)
TestWatcher监控测试结果(成功、失败、跳过)日志记录、报告生成

三、自定义扩展示例

示例 1:自定义 BeforeEachCallback 扩展

功能:在每个测试方法执行前打印日志。

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

public class LoggingExtension implements BeforeEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        System.out.println("开始执行测试: " + context.getDisplayName());
    }
}

// 使用扩展
@ExtendWith(LoggingExtension.class)
class LoggingTest {

    @Test
    void test1() {
        System.out.println("执行测试1");
    }

    @Test
    void test2() {
        System.out.println("执行测试2");
    }
}

输出

开始执行测试: test1
执行测试1
开始执行测试: test2
执行测试2

示例 2:自定义 @Random 注解(通过 ParameterResolver
步骤 1:定义 @Random 注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Random {
    int min() default 0;
    int max() default 100;
}
步骤 2:实现 ParameterResolver
import org.junit.jupiter.api.extension.*;
import java.util.Random;

public class RandomParameterResolver implements ParameterResolver {

    private final Random random = new Random();

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return parameterContext.getParameter().isAnnotationPresent(Random.class);
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        Random annotation = parameterContext.getParameter().getAnnotation(Random.class);
        int min = annotation.min();
        int max = annotation.max();
        return random.nextInt(max - min + 1) + min;
    }
}
步骤 3:使用 @Random 注解
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(RandomParameterResolver.class)
class RandomTest {

    @Test
    void testWithRandomNumber(@Random(min = 10, max = 20) int number) {
        Assertions.assertTrue(number >= 10 && number <= 20);
    }
}

示例 3:自定义 TestWatcher 扩展

功能:记录测试结果并生成报告。

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

public class ReportingExtension implements TestWatcher {

    @Override
    public void testSuccessful(ExtensionContext context) {
        log("测试成功: " + context.getDisplayName());
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable cause) {
        log("测试失败: " + context.getDisplayName() + ", 原因: " + cause.getMessage());
    }

    private void log(String message) {
        System.out.println("[REPORT] " + message);
    }
}

// 使用扩展
@ExtendWith(ReportingExtension.class)
class ReportingTest {

    @Test
    void successTest() {
        Assertions.assertTrue(true);
    }

    @Test
    void failTest() {
        Assertions.fail("预期失败");
    }
}

输出

[REPORT] 测试成功: successTest
[REPORT] 测试失败: failTest, 原因: 预期失败

示例 4:自定义 TestExecutionCondition 扩展

功能:根据环境变量跳过测试。

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

public class EnvCheckCondition implements TestExecutionCondition {

    @Override
    public ConditionEvaluationResult evaluate(TestExecutionConditionContext context) {
        String env = System.getenv("ENV");
        if ("prod".equals(env)) {
            return ConditionEvaluationResult.disabled("生产环境跳过测试");
        }
        return ConditionEvaluationResult.enabled("执行测试");
    }
}

// 使用扩展
@ExtendWith(EnvCheckCondition.class)
class ConditionalTest {

    @Test
    void testOnlyInNonProd() {
        Assertions.assertTrue(true);
    }
}

四、全局注册扩展

通过 META-INF/services 配置文件自动加载扩展,无需显式注解。

  1. 创建配置文件
    src/test/resources/META-INF/services 目录下创建文件:
    org.junit.jupiter.api.extension.Extension

  2. 配置扩展类
    在文件中写入扩展类的全限定名:

    com.example.LoggingExtension
    com.example.RandomNumberResolver
    
  3. 使用扩展
    测试类中无需 @ExtendWith 注解,扩展会自动生效。


五、扩展的执行顺序

通过 @Order 注解控制多个扩展的执行顺序:

@Order(1)
public class ExtensionA implements BeforeEachCallback { ... }

@Order(2)
public class ExtensionB implements BeforeEachCallback { ... }

六、总结

自定义扩展的核心价值

  • 模块化:将通用逻辑封装为可重用的扩展。
  • 灵活性:支持按需组合功能,避免代码冗余。
  • 生态兼容:无缝集成主流框架(如 Spring、Mockito)。

推荐实践

  • 优先使用成熟的第三方扩展(如 Spring、Mockito 的扩展)。
  • 在需要复用逻辑时开发自定义扩展。
  • 通过组合扩展实现复杂测试需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964824.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>黄金矿工

目录 决策树&#xff1a;代码设计代码&#xff1a; 决策树&#xff1a; 代码设计 代码&#xff1a; class Solution {boolean[][] vis;int ret,m,n;public int getMaximumGold(int[][] grid) {m grid.length;n grid[0].length;vis new boolean[m][n]; for(int i 0; i <…

DeepSeek 的含金量还在上升

大家好啊&#xff0c;我是董董灿。 最近 DeepSeek 越来越火了。 网上有很多针对 DeepSeek 的推理测评&#xff0c;除此之外&#xff0c;也有很多人从技术的角度来探讨 DeepSeek 带给行业的影响。 比如今天就看到了一篇文章&#xff0c;探讨 DeepSeek 在使用 GPU 进行模型训练…

使用SpringBoot发送邮件|解决了部署时连接超时的bug|网易163|2025

使用SpringBoot发送邮件 文章目录 使用SpringBoot发送邮件1. 获取网易邮箱服务的授权码2. 初始化项目maven部分web部分 3. 发送邮件填写配置EmailSendService [已解决]部署时连接超时附&#xff1a;Docker脚本Dockerfile创建镜像启动容器 1. 获取网易邮箱服务的授权码 温馨提示…

两种文件类型(pdf/图片)打印A4半张纸方法

环境:windows10、Adobe Reader XI v11.0.23 Pdf: 1.把内容由横排变为纵排&#xff1a; 2.点击打印按钮&#xff1a; 3.选择打印页范围和多页&#xff1a; 4.内容打印在纸张上部 图片&#xff1a; 1.右键图片点击打印&#xff1a; 2.选择打印类型&#xff1a; 3.打印配置&am…

C语言打印输出星号图形(三角形、菱形、漏斗)

文章目录 1. 介绍2. 案例分析3. 漏斗型4. 直角三角形4.1 左上直角三角形4.2 右上直角三角形4.3 左下直角三角形4.4 右下直角三角形 5. 等腰三角形5.1 正等腰三角形5.2 倒等腰三角形 6. 平行四边形6.1 纵向左下平行四边形6.2 纵向左上平行四边形6.3 横向左上平行四边形6.4 横向左…

刷题记录 动态规划-7: 63. 不同路径 II

题目&#xff1a;63. 不同路径 II 难度&#xff1a;中等 给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角&#xff08;即 grid[0][0]&#xff09;。机器人尝试移动到 右下角&#xff08;即 grid[m - 1][n - 1]&#xff09;。机器人每次只能向下或者向右移动一步。…

springboot+vue+uniapp的校园二手交易小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

Python 自学秘籍:开启编程之旅,人生苦短,我用python。

从2009年&#xff0c;用了几次python后就放弃了&#xff0c;一直用的php&#xff0c;现在人工智能时代&#xff0c;完全没php什么事情。必须搞python了&#xff0c;虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代&#xff…

react的antd表格自定义图标

将原版的加号换成箭头 自定义图标 安装图标包&#xff1a; npm install --save ant-design/icons 引入&#xff1a; import { RightOutlined, DownOutlined } from ant-design/icons; 参数是一个函数 <Table columns{columns} dataSource{data} indentSize{20}expandIc…

chrome浏览器chromedriver下载

chromedriver 下载地址 https://googlechromelabs.github.io/chrome-for-testing/ 上面的链接有和当前发布的chrome浏览器版本相近的chromedriver 实际使用感受 chrome浏览器会自动更新&#xff0c;可以去下载最新的chromedriver使用&#xff0c;自动化中使用新的chromedr…

Vim的基础命令

移动光标 H(左) J(上) K(下) L(右) $ 表示移动到光标所在行的行尾&#xff0c; ^ 表示移动到光标所在行的行首的第一个非空白字符。 0 表示移动到光标所在行的行首。 W 光标向前跳转一个单词 w光标向前跳转一个单词 B光标向后跳转一个单词 b光标向后跳转一个单词 G 移动光标到…

算法与数据结构(括号匹配问题)

思路 从题干可以看出&#xff0c;只要给出的括号对应关系正确&#xff0c;那么就可以返回true,否则返回false。这个题可以使用栈来解决 解题过程 首先从第一个字符开始遍历&#xff0c;如果是括号的左边&#xff08;‘&#xff08;‘&#xff0c;’[‘&#xff0c;’}‘&…

deepseek、qwen等多种模型本地化部署

想要在本地部署deepseek、qwen等模型其实很简单,快跟着小编一起部署吧 1 环境搭建 1.1下载安装环境 首先我们需要搭建一个环境ollama,下载地址如下 :Ollama 点击Download 根据自己电脑的系统选择对应版本下载即可 1.2 安装环境(window为例) 可以直接点击安装包进行安…

(2025,LLM,下一 token 预测,扩散微调,L2D,推理增强,可扩展计算)从大语言模型到扩散微调

Large Language Models to Diffusion Finetuning 目录 1. 概述 2. 研究背景 3. 方法 3.1 用于 LM 微调的高斯扩散 3.2 架构 4. 主要实验结果 5. 结论 1. 概述 本文提出了一种新的微调方法——LM to Diffusion (L2D)&#xff0c;旨在赋予预训练的大语言模型&#xff08;…

一款wordpress AI免费插件自动内容生成+前端AI交互+文章批量采集

一款wordpressAI自动内容生成前端AI会话窗口交互文章批量采集免费插件 1. SEO优化文章生成 关键词驱动的内容生成&#xff1a;用户可以输入关键词或长尾关键词&#xff0c;插件会根据这些关键词生成高质量的SEO优化文章。文章结构清晰&#xff0c;语言自然流畅&#xff0c;符合…

2024年12月 Scratch 图形化(一级)真题解析 中国电子学会全国青少年软件编程等级考试

202412 Scratch 图形化&#xff08;一级&#xff09;真题解析 中国电子学会全国青少年软件编程等级考试 一、单选题(共25题&#xff0c;共50分) 第 1 题 点击下列哪个按钮&#xff0c;可以将红框处的程序放大&#xff1f;&#xff08; &#xff09; A. B. C. D. 标…

Java面试题集合篇5:10道基础面试题——保姆级详细图文、代码解释

文章目录 前言41、多线程场景下使用 ArrayList42、List 和 Set 区别43、HashSet 实现原理44、HashSet检查重复和保证数据不可重复45、BlockingQueue46、Map接口46.1、HashMap 实现原理46.2、HashMap在JDK1.7和JDK1.8中不同点46.3、JDK1.7 VS JDK1.8 比较 47、HashMap的put方法流…

2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题2)-网络部分解析-附详细代码

目录 附录1:拓扑图​编辑 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.SW5 6.SW6 7.SW7 8.R1 9.R2 10.R3 11.AC1 12.AC2 13.EG1 14.EG2 15.AP2 16.AP3 附录1:拓扑图 附录2:地址规划表

优化数据库结构

MySQL学习大纲 一个好的数据库设计方案对于数据库的性能尝尝会起到事倍功半的效果&#xff0c;合理的数据库结构不仅使数据库占用更小的磁盘空间&#xff0c;而且使查询速度更快。数据库结构的设计需要考虑数据冗余、查询和更新速度、字段的数据类型是否合理等多方面的内容&…

【deepseek实战】绿色好用,不断网

前言 最佳deepseek火热网络&#xff0c;我也开发一款windows的电脑端&#xff0c;接入了deepseek&#xff0c;基本是复刻了网页端&#xff0c;还加入一些特色功能。 助力国内AI&#xff0c;发出自己的热量 说一下开发过程和内容的使用吧。 目录 一、介绍 二、具体工作 1.1、引…