JUnit 最佳实践:让测试真正有用
测试不是写完就完事了。不规范的测试,时间久了就没人敢改,成了遗留代码的温床。
这节聊怎么把测试写规范、可维护。
测试命名规范
好的测试名应该清楚说明在什么条件下、测什么行为:
java
// ❌ 无意义的测试名
@Test
void test1() { }
@Test
void testSave() { } // save 什么?
// ✅ 有意义的测试名
@Test
@DisplayName("保存用户 - 成功")
void saveUser_Success() { }
@Test
@DisplayName("保存用户 - 邮箱格式无效")
void saveUser_InvalidEmail_ThrowsException() { }
@Test
@DisplayName("保存用户 - 用户名为空")
void saveUser_EmptyName_ThrowsException() { }测试分层
不同层次的测试关注点不同:
| 层次 | 测什么 | 典型工具 |
|---|---|---|
| 单元测试 | 单个类/方法 | JUnit + Mockito |
| 集成测试 | 多组件协作 | Spring Boot Test |
| 端到端测试 | 完整业务流程 | Selenium |
测试数据准备
测试数据工厂
避免每个测试里重复创建相似对象:
java
public class TestDataFactory {
public static User createUser(String name) {
return User.builder()
.name(name)
.email(name + "@example.com")
.createdAt(LocalDateTime.now())
.build();
}
public static List<User> createUsers(int count) {
return IntStream.range(0, count)
.mapToObj(i -> createUser("User" + i))
.collect(Collectors.toList());
}
}
// 使用
@Test
void testProcessUsers() {
List<User> users = TestDataFactory.createUsers(10);
// ...
}共享测试数据 @BeforeEach
java
class UserServiceTest {
private UserService userService;
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
userService = new UserService(userRepository);
}
}测试分类 @Tag
用 Tag 把测试分类,CI 里可以按类跑:
java
@Tag("unit")
@Test
void unitTest() { }
@Tag("integration")
@Test
void integrationTest() { }
@Tag("slow")
@Test
void slowTest() { }CI 配置示例:
bash
# 只跑单元测试
mvn test -Dgroups=unit
# 跳过慢测试
mvn test -DexcludedGroups=slow总结
测试规范的核心:
- 命名有意义:让人一眼看出在测什么
- 每个测试只测一件事:单一职责
- 测试独立:不依赖其他测试的执行顺序
- 可重复:每次运行结果一致
测试的维护成本往往比写测试本身还高,所以从一开始就把测试写规范。
