Skip to content

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

总结

测试规范的核心:

  1. 命名有意义:让人一眼看出在测什么
  2. 每个测试只测一件事:单一职责
  3. 测试独立:不依赖其他测试的执行顺序
  4. 可重复:每次运行结果一致

测试的维护成本往往比写测试本身还高,所以从一开始就把测试写规范。

基于 VitePress 构建