单元测试编写规范总结
单元测试编写规范适用版本Spring Boot 2.2.13JUnit 5.6.3Mockito 3.6.28Java 8作用域全模块 Java 单元测试编写标准本文参考业内大佬总结1.黑白盒测试—2.Java 全链路测试体系1. 总则本规范适用于常见 java 项目模块的纯单元测试编写目标是统一风格所有模块的测试代码结构一致任何开发者都能快速读懂。高可维护性测试即文档用例名称本身就是测试场景的描述。高可信度通过严格的隔离策略和异常覆盖确保测试真正验证了业务逻辑。1.1 技术栈约束组件版本说明JUnit Jupiter5.6.3测试框架Java 8 兼容的最后稳定版Mockito3.6.28Mock 框架Java 8 兼容的最后稳定版AssertJ3.18.1流式断言库可选推荐Spring Boot Test2.2.13.RELEASE仅集成测试使用单元测试禁止启动 Spring为什么锁定 Java 8 兼容版本本项目基于 JDK 1.8Mockito 5.x 和 JUnit 5.11 均要求 Java 11强行使用会导致编译/运行时报错。2. 测试分类与 Tag 规范2.1 自定义 Tag 注解强制所有测试类必须使用项目统一定义的 Tag 注解禁止裸写Tag(xxx)。可在公共依赖模块的 xxxx.test 包下预定义注解// 纯单元测试仅使用 Mockito无任何外部中间件依赖Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)Tag(unit)publicinterfaceUnitTest{}// 集成测试依赖 DB / Redis / MQ / HTTP 接口等外部中间件Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)Tag(integration)publicinterfaceIntegrationTest{}2.2 分类判定标准场景分类判定依据工具类AESUtil、StringUtilsUnitTest纯计算无任何外部依赖Service 方法Mock 掉 MapperUnitTest仅依赖 MapperMock 后无真实 DB 交互Service → Mapper 真实读写IntegrationTest需要连接数据库MinIO / Redis / MQTT 交互IntegrationTest需要连接外部中间件Controller HTTP 契约验证IntegrationTest需要启动 Spring 上下文2.3 使用示例importcommon.test.UnitTest;// ✅ 正确使用自定义注解UnitTestExtendWith(MockitoExtension.class)DisplayName(AES 工具类单元测试)classAesTest{...}// ❌ 错误裸写 TagTag(unit)ExtendWith(MockitoExtension.class)classAesTest{...}3. 命名规范3.1 测试类命名类型命名格式示例单元测试被测类名 TestSysUserServiceImplTest、AESUtilTest集成测试被测类名 ITSysConfigServiceImplIT接口测试被测Controller ApiTestSysUserControllerApiTest3.2 测试方法命名强制格式被测方法名_输入条件_预期结果采用驼峰命名三段之间用下划线_分隔。方法名含义getUserById_NullUserId_ThrowsIllegalArgumentException传入 null 用户ID预期抛出非法参数异常setUserPassword_EmptyPassword_ThrowsServiceException密码为空预期抛出业务异常selectConfigByKey_ValidKey_ReturnsTypedValue合法 key预期返回类型正确的值encrypt_ValidInput_ReturnsBase64Cipher合法输入加密预期返回 Base64 密文list_NullParam_ReturnsEmptyList参数为 null预期返回空列表反面示例禁止// ❌ 含义不明Testvoidtest1(){...}// ❌ 无法看出输入和预期TestvoidtestGetUser(){...}// ❌ 缺少输入条件段TestvoidgetUserById_ReturnsUser(){...}3.3 DisplayName强制每个测试方法必须添加DisplayName用中文自然语言描述测试场景TestDisplayName(用户ID为null时抛出IllegalArgumentException)voidgetUserById_NullUserId_ThrowsIllegalArgumentException(){...}4. AAA 模式强制每个测试方法必须严格遵循Arrange → Act → Assert三段式结构三段之间用空行分隔。4.1 结构说明阶段职责包含内容Arrange准备构造测试数据、配置 Mock 行为when(...).thenReturn(...)、对象构建Act执行调用被测方法一行核心调用Assert断言验证结果assertXxx、verify4.2 完整示例UnitTestExtendWith(MockitoExtension.class)DisplayName(用户服务单元测试)classSysUserServiceImplTest{MockprivateSysUserMapperuserMapper;InjectMocksprivateSysUserServiceImpluserService;TestDisplayName(根据合法ID查询用户返回对应用户信息)voidgetById_ValidUserId_ReturnsUser(){// Arrange SysUserexpectedUsernewSysUser();expectedUser.setUserId(1L);expectedUser.setUserName(zhangsan);when(userMapper.selectById(1L)).thenReturn(expectedUser);// Act SysUseractualUseruserService.getById(1L);// Assert assertThat(actualUser).isNotNull();assertThat(actualUser.getUserId()).isEqualTo(1L);assertThat(actualUser.getUserName()).isEqualTo(zhangsan);verify(userMapper).selectById(1L);}}4.3 AAA 各段禁忌禁忌说明Arrange 中调用被测方法准备阶段不应触发被测逻辑Act 段包含断言执行和验证必须分离Assert 段构造新数据断言阶段只验证不准备多个 Act 调用一个用例只测一个行为路径5. Mock 规范5.1 只 Mock 直接依赖核心原则Mock 的边界是被测类的直接外部依赖不要穿透到依赖的依赖。// ✅ 正确UserService 直接依赖 UserMapper只 Mock MapperMockprivateSysUserMapperuserMapper;InjectMocksprivateSysUserServiceImpluserService;// ❌ 错误穿透 Mock 了 Mapper 依赖的 DataSource / SqlSessionMockprivateDataSourcedataSource;MockprivateSqlSessionsqlSession;判断标准如果被测类通过Autowired直接注入了某个 Bean就 Mock 它如果是间接依赖被注入的 Bean 内部使用的不要 Mock。5.2 Mock 行为配置规范// ✅ 精确匹配when(userMapper.selectById(1L)).thenReturn(expectedUser);// ✅ 参数匹配器需要灵活匹配时when(userMapper.selectById(anyLong())).thenReturn(expectedUser);// ✅ Mock 异常行为when(userMapper.selectById(anyLong())).thenThrow(newRuntimeException(DB error));// ❌ 禁止在 when 中执行真实业务逻辑when(userMapper.selectById(anyLong())).thenCallRealMethod();// 单元测试中禁止5.3 verify 使用规范// ✅ 验证交互次数verify(userMapper,times(1)).selectById(1L);// ✅ 验证从未调用verify(userMapper,never()).deleteById(anyLong());// ✅ 验证无更多交互verifyNoMoreInteractions(userMapper);原则verify用于验证行为发生了assert用于验证结果正确了。两者互补不要混用。6. 断言规范6.1 优先使用 AssertJ 流式断言AssertJ 提供可读性更强的链式断言推荐在单元测试中使用// ✅ 推荐AssertJ 流式断言assertThat(user).isNotNull();assertThat(user.getUserName()).isEqualTo(zhangsan);assertThat(userList).hasSize(3).extracting(SysUser::getUserName).contains(zhangsan);// ✅ 可接受JUnit 5 原生断言assertNotNull(user);assertEquals(zhangsan,user.getUserName());// ❌ 禁止JUnit 4 断言org.junit.AssertAssert.assertNotNull(user);// 必须迁移到 JUnit 56.2 断言必须明确// ✅ 明确断言assertThat(result.getCode()).isEqualTo(200);assertThat(result.getData()).isNotNull();assertThat(result.getData().getUserId()).isEqualTo(1L);// ❌ 模糊断言太弱无法发现问题assertNotNull(result);6.3 常用断言速查// 空值判断assertThat(result).isNull();assertThat(result).isNotNull();// 集合判断assertThat(list).isEmpty();assertThat(list).hasSize(3);assertThat(list).containsExactly(item1,item2,item3);// 字符串判断assertThat(str).startsWith(prefix);assertThat(str).contains(keyword);// 数值判断assertThat(count).isGreaterThan(0);assertThat(score).isBetween(0.0,100.0);// 异常断言assertThatThrownBy(()-service.doSomething(null)).isInstanceOf(ServiceException.class).hasMessageContaining(不能为空);7. 异常场景覆盖强制所有公开方法的异常路径必须有用例覆盖。这是 bug 的高发区。7.1 必须覆盖的异常类型异常类型说明示例参数校验异常入参为 null、空字符串、非法值setUserPassword(null)→ServiceException业务异常违反业务规则删除已有角色的用户 →ServiceException边界条件空集合、最大长度、零值、负数selectById(-1L)外部依赖异常Mock 的依赖抛出异常Mapper 抛RuntimeException7.2 异常测试示例TestDisplayName(密码为空时抛出ServiceException)voidsetUserPassword_EmptyPassword_ThrowsServiceException(){// Arrange SysUserusernewSysUser();user.setPassword();// Act Assert assertThatThrownBy(()-userService.setUserPassword(user)).isInstanceOf(ServiceException.class).hasMessageContaining(密码不能为空);}TestDisplayName(用户对象为null时抛出ServiceException)voidsetUserPassword_NullUser_ThrowsServiceException(){// Act Assert assertThatThrownBy(()-userService.setUserPassword(null)).isInstanceOf(ServiceException.class).hasMessageContaining(密码不能为空);}TestDisplayName(Mapper查询异常时异常向上传播)voidgetById_MapperThrowsException_PropagatesException(){// Arrange when(userMapper.selectById(anyLong())).thenThrow(newRuntimeException(数据库连接失败));// Act Assert assertThatThrownBy(()-userService.getById(1L)).isInstanceOf(RuntimeException.class).hasMessageContaining(数据库连接失败);}8. 不测试私有方法8.1 原则私有方法是类的内部实现细节不应直接测试。如果某个私有方法复杂到需要独立测试说明该类职责过大应进行重构将私有方法提取到一个新的工具类/服务类中将其改为public方法为新类编写独立的单元测试8.2 重构示例// ❌ 重构前私有方法难以测试publicclassDeviceService{publicDeviceStatusparseStatus(StringrawData){StringnormalizednormalizeData(rawData);// 私有方法returnconvertToStatus(normalized);// 私有方法}privateStringnormalizeData(Stringraw){...}// 逻辑复杂需要测试privateDeviceStatusconvertToStatus(Stringdata){...}}// ✅ 重构后拆分为独立类publicclassDeviceService{privatefinalDeviceDataParserdataParser;publicDeviceService(DeviceDataParserdataParser){this.dataParserdataParser;}publicDeviceStatusparseStatus(StringrawData){StringnormalizeddataParser.normalizeData(rawData);returndataParser.convertToStatus(normalized);}}// 新类可独立测试publicclassDeviceDataParser{publicStringnormalizeData(Stringraw){...}publicDeviceStatusconvertToStatus(Stringdata){...}}9. 测试隔离与生命周期9.1 测试独立性每个测试方法必须独立运行不依赖其他测试的执行顺序或共享状态。// ✅ 正确每个测试方法独立构造数据TestvoidmethodA_ValidInput_ReturnsResult(){SysUserusernewSysUser();// 独立构造user.setUserId(1L);// ...}TestvoidmethodB_ValidInput_ReturnsResult(){SysUserusernewSysUser();// 独立构造不复用 methodA 的数据user.setUserId(2L);// ...}9.2 BeforeEach / AfterEach 使用仅在所有测试方法共享相同初始化/清理逻辑时使用BeforeEachvoidsetUp(){// 所有测试方法共享的 Mock 配置when(userMapper.selectById(anyLong())).thenReturn(buildDefaultUser());}AfterEachvoidtearDown(){// 清理资源如果有}9.3 禁止事项禁止行为原因测试方法之间共享可变状态执行顺序不确定导致结果不稳定在测试中访问文件系统/网络破坏隔离性使用 Mock 替代在测试中 sleep 等待使用Awaitility或重构为可测试的设计硬编码环境相关配置使用ActiveProfiles(test)或 Mock10. 落地检查清单每个测试类提交前对照以下清单自检类级别标注UnitTest或IntegrationTest禁止裸写Tag类级别标注DisplayName(中文描述)每个方法命名格式为方法名_条件_预期结果每个方法标注DisplayName(中文描述)遵循 AAA 模式三段之间有空行分隔只 Mock 了被测类的直接依赖正常路径、参数校验异常、业务异常、边界条件均有覆盖未测试私有方法如需测试则重构拆分断言明确不使用assertNotNull一笔带过无硬编码环境配置IP、端口、密码等11. CI 执行命令# 只跑单元测试mvntest-Dgroupsunit# 只跑集成测试mvn verify-Dgroupsintegration# 跑全部测试mvntest# 跳过测试快速打包mvn package-DskipTests

相关新闻

ruoyi-vue初步接触

ruoyi-vue初步接触

ruoyi-vue初步接触 引言:从零开始认识ruoyi-vue对于刚接触Java全栈开发的同学来说,“ruoyi-vue”这个名字或许有些陌生。实际上,它是一个基于Spring Boot、Spring Security、MyBatis和Vue.js的开源后台管理系统框架。通过它,我们可…

2026/7/26 23:43:42 阅读更多 →
别把鸡蛋放一个板块:用 Python 计算持仓相关性 + 集中度预警,把风险关进笼子里

别把鸡蛋放一个板块:用 Python 计算持仓相关性 + 集中度预警,把风险关进笼子里

本文为量化风控方法研究,所有价格与收益序列均为示意版(模拟数据),不构成任何投资建议。代码可直接运行,用于学习"相关性监控 集中度预警"的思路,非实盘交易依据,不涉及任何真实策略…

2026/7/26 23:43:42 阅读更多 →
AlphaFold技术解析:从蛋白质结构预测到生物医学革命

AlphaFold技术解析:从蛋白质结构预测到生物医学革命

1. AlphaFold技术革命全景回顾 2018年那个潮湿的伦敦夏天,DeepMind团队在蛋白质结构预测竞赛CASP13上扔下了一枚"AI炸弹"。当时我在结构生物学实验室亲眼目睹同事们传阅预测结果时脸上难以置信的表情——那些蓝色和绿色的带状图与实验测得的真实结构几乎完…

2026/7/26 23:42:42 阅读更多 →

最新新闻

深度解析R3nzSkin:英雄联盟皮肤修改器的技术架构与内核级反作弊对抗实战指南

深度解析R3nzSkin:英雄联盟皮肤修改器的技术架构与内核级反作弊对抗实战指南

深度解析R3nzSkin:英雄联盟皮肤修改器的技术架构与内核级反作弊对抗实战指南 【免费下载链接】R3nzSkin Skin changer for League of Legends (LOL) 项目地址: https://gitcode.com/gh_mirrors/r3/R3nzSkin 在游戏修改工具与反作弊系统的永恒博弈中&#xff…

2026/7/27 0:03:56 阅读更多 →
告别枯燥刷本!三月七小助手让星穹铁道游戏时间减少80%

告别枯燥刷本!三月七小助手让星穹铁道游戏时间减少80%

告别枯燥刷本!三月七小助手让星穹铁道游戏时间减少80% 【免费下载链接】March7thAssistant 崩坏:星穹铁道全自动 三月七小助手 项目地址: https://gitcode.com/gh_mirrors/ma/March7thAssistant 你是否已经厌倦了每天重复刷材料、清体力的机械操作…

2026/7/27 0:03:56 阅读更多 →
剪映AI音频分离功能即将下线?内部消息证实:6月起全面接入火山引擎ASR-AI架构(附迁移保底方案)

剪映AI音频分离功能即将下线?内部消息证实:6月起全面接入火山引擎ASR-AI架构(附迁移保底方案)

更多请点击: https://intelliparadigm.com 第一章:剪映AI音频分离功能即将下线?内部消息证实:6月起全面接入火山引擎ASR-AI架构(附迁移保底方案) 功能变更背景与时间节点确认 据字节跳动内部技术通告&…

2026/7/27 0:03:56 阅读更多 →
【可灵多镜头短片制作终极指南】:20年影像工程师亲授3步实现电影级分镜调度与无缝转场

【可灵多镜头短片制作终极指南】:20年影像工程师亲授3步实现电影级分镜调度与无缝转场

更多请点击: https://codechina.net 第一章:可灵多镜头短片制作的核心理念与技术演进 可灵(Kling)作为新一代AI原生视频生成模型,其多镜头短片能力突破了传统单帧/单镜头生成范式,转向以叙事逻辑驱动的时…

2026/7/27 0:03:56 阅读更多 →
优启通3.7修改版:深度优化的PE系统维护工具

优启通3.7修改版:深度优化的PE系统维护工具

1. 项目概述今天要跟大家分享的是一个经过深度优化的PE工具——优启通3.7(2025修改版)。这个版本是在原版基础上进行了大量功能增强和兼容性改进的12月最新版本,特别适合系统维护人员和电脑爱好者使用。作为一个长期从事IT运维的老兵&#xf…

2026/7/27 0:03:56 阅读更多 →
Android 串口底层基础:USB 串口、RS485/232 驱动识别、串口通信框架入门

Android 串口底层基础:USB 串口、RS485/232 驱动识别、串口通信框架入门

Android 串口底层基础:USB 串口、RS485/232 驱动识别、串口通信框架入门工控板子要跟传感器、继电器、PLC 对话,串口就是那根"嗓子"。搞不定串口,工控项目等于哑巴。一、串口通信基础 串口通信是最经典的设备间通信方式&#xff0c…

2026/7/27 0:02:56 阅读更多 →

日新闻

【JAVA毕设源码分享】基于SpringBoot的社区智能垃圾管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于SpringBoot的社区智能垃圾管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:54 阅读更多 →
SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

1. 项目概述:从寄存器手册到实战指南 如果你手头有一份类似德州仪器(TI)TMS320x240xA系列DSP的SPI模块技术手册,看着里面密密麻麻的寄存器位定义、时序图和公式,是不是感觉头大?这份资料虽然权威&#xff0…

2026/7/27 0:00:54 阅读更多 →
【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:54 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/26 0:00:31 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/26 0:00:31 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/26 0:00:31 阅读更多 →

月新闻