核心内容摘要
搞机Time:不止于心动,更是生活的魔法棒
视频看了几百小时还迷糊关注我几分钟让你秒懂发点评论可以给博主加热度哦
真实痛点为什么你总在逃避写单元测试“业务太复杂不知道怎么测”“写了测试改代码又要改测试太麻烦”“跑一次测试要连数据库、启动 Spring慢得像蜗牛”“测了也没用线上照样出 bug”其实你不是不会写测试而是没掌握“正确的姿势”本文将通过真实业务场景 正反案例对比手把手教你写出快、准、稳的单元测试
什么是单元测试一句话讲透单元测试 对一个“最小可测试单元”通常是方法进行隔离验证确保它在各种输入下都能正确工作。
✅ 核心原则快毫秒级执行不依赖外部DB、网络隔离只测目标方法其他依赖全部 Mock可重复每次运行结果一致全覆盖正常流 异常流都要测。
反例警告这些“伪测试”你一定写过❌ 反例 1启动整个 Spring 容器集成测试冒充单元测试SpringBootTest // ←←← 错这是集成测试 class UserServiceTest { Test void testRegister() { // 连了数据库、Redis、MQ... // 跑一次 10 秒谁受得了 } }❌ 反例 2只测 happy path正常流程不测异常Test void testAdd() { assertEquals(5, calculator.add(2,
); // 只测了正数 // 没测负数、零、溢出... }❌ 反例 3测试代码和业务代码强耦合// 业务代码改了字段名测试全红 assertEquals(张三, user.getName()); 这些都不是真正的单元测试
手把手实战用 JUnit 5 Mockito 写纯单元测试场景用户注册服务含手机号校验1️⃣ 业务代码待测试Service public class UserService { private final UserRepository userRepository; private final SmsService smsService; public UserService(UserRepository userRepository, SmsService smsService) { this.userRepository userRepository; this.smsService smsService; } public User register(String phone, String name) { if (userRepository.existsByPhone(phone)) { throw new IllegalArgumentException(手机号已存在); } if (!isValidPhone(phone)) { throw new IllegalArgumentException(手机号格式错误); } User user new User(phone, name); userRepository.save(user); smsService.sendWelcomeSms(phone); // 发欢迎短信 return user; } private boolean isValidPhone(String phone) { return phone ! null phone.matches(^1[
]\\d{9}$); } }2️⃣ 单元测试纯内存0 依赖import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; ExtendWith(MockitoExtension.class) // 启用 Mockito class UserServiceTest { Mock private UserRepository userRepository; Mock private SmsService smsService; InjectMocks private UserService userService; // 自动注入 mock 依赖 Test DisplayName(注册成功手机号合法且未注册) void shouldRegisterSuccessfully() { // Given准备 String phone 13800138000; String name 张三; when(userRepository.existsByPhone(phone)).thenReturn(false); // When执行 User result userService.register(phone, name); // Then断言 assertNotNull(result); assertEquals(phone, result.getPhone()); assertEquals(name, result.getName()); verify(userRepository).save(any(User.class)); // 验证 save 被调用 verify(smsService).sendWelcomeSms(phone); // 验证短信被发送 } Test DisplayName(注册失败手机号已存在) void shouldThrowExceptionWhenPhoneExists() { // Given String phone 13800138000; when(userRepository.existsByPhone(phone)).thenReturn(true); // When Then IllegalArgumentException ex assertThrows( IllegalArgumentException.class, () - userService.register(phone, 李
); assertEquals(手机号已存在, ex.getMessage()); verify(userRepository, never()).save(any()); // 确保没保存 verify(smsService, never()).sendWelcomeSms(any()); // 确保没发短信 } Test DisplayName(注册失败手机号格式错误) void shouldThrowExceptionWhenPhoneInvalid() { // 测试多种非法手机号 assertInvalidPhone(null); assertInvalidPxone(); assertInvalidPhone(
; assertInvalidPhone(
; // 少一位 assertInvalidPhone(
; // 开头不是 1 } private void assertInvalidPhone(String phone) { IllegalArgumentException ex assertThrows( IllegalArgumentException.class, () - userService.register(phone, 王
); assertEquals(手机号格式错误, ex.getMessage()); } }✅优势0 数据库用Mock模拟依赖毫秒级运行整个测试类 100ms覆盖全面成功 两种失败场景验证行为不仅看返回值还验证save和sendSms是否被正确调用。
关键工具介绍注解/方法作用ExtendWith(MockitoExtension.class)启用 MockitoMock创建 mock 对象模拟依赖InjectMocks创建被测对象并自动注入 mock 依赖when(...).thenReturn(...)定义 mock 行为verify(...).method(...)验证方法是否被调用assertThrows断言抛出异常never()验证方法从未被调用
高级技巧测试私有方法别傻了很多新手问“怎么测isValidPhone私有方法”✅正确答案不要直接测私有方法私有方法是实现细节应该通过公有方法间接测试如果私有方法逻辑复杂说明它该提取成独立工具类// 提取成工具类可单独测试 public class PhoneUtils { public static boolean isValid(String phone) { return phone ! null phone.matches(^1[
]\\d{9}$); } } // 然后在 UserService 中调用 if (!PhoneUtils.isValid(phone)) { ... } // 单独测试 PhoneUtils class PhoneUtilsTest { Test void testValidPhone() { ... } }原则测试行为而不是实现
集成测试 vs 单元测试 —— 别再混淆类型注解速度用途单元测试ExtendWith(MockitoExtension.class)⚡ 毫秒级测单个类逻辑集成测试SpringBootTest 几秒~几十秒测整个 Spring 上下文、DB 交互✅建议比例单元测试80%快、稳、易维护集成测试20%验证关键链路
完整项目结构Maven!-- pom.xml -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency默认包含JUnit 5MockitoAssertJHamcrest
最佳实践
总结命名清晰shouldDoXxxWhenYyy如shouldThrowExceptionWhenPhoneExists三段式结构Given准备→ When执行→ Then断言一个测试只测一个场景不要在一个Test里测多个逻辑Mock 外部依赖DB、HTTP、MQ 全部模拟覆盖边界条件null、空、超长、特殊字符不要测 getter/setter除非有逻辑
常见误区❌ “测试覆盖率越高越好”错100% 覆盖但没测到关键路径不如 70% 覆盖但测了所有分支。
❌ “测试代码不用维护”错烂测试比没测试更可怕给人虚假安全感。
✅ 正确心态“写测试是为了让自己改代码时睡得着觉”视频看了几百小时还迷糊关注我几分钟让你秒懂发点评论可以给博主加热度哦