场景开拍:想象你是一位正在拍摄代码大片的导演,每个@Test方法就是一个拍摄镜头,参数化测试是你的特效工厂,而Assertions则是你的监视器。今天让我们打开JUnit 5的导演宝典,学习如何用专业手法打造零NG的完美测试!
import org.junit.jupiter.api.*;
class DirectorToolkitTest {
@BeforeAll // 制片人:全剧组的准备工作
static void setupCinema() {
System.out.println("租赁拍摄场地");
}
@BeforeEach // 场务:每个镜头的布景
void prepareScene() {
System.out.println("摆放摄影器材");
}
@Test // 正式拍摄镜头
void shootActionScene() {
System.out.println("Action!");
}
}
@DisplayName(" 用户注册流程") // 给测试剧本起个好名字
@Tag("critical-path") // 标注拍摄类型
@TestInstance(Lifecycle.PER_CLASS) // 启用好莱坞式长镜头
class RegistrationTest {
// 特别适合需要维护状态的测试场景
}
@Test
void checkMovieQuality() {
Film film = filmService.produceFilm();
// 基础画质检查
assertNotNull(film.getDirector(), "必须指定导演");
// 高清参数验证
assertAll("电影规格",
() -> assertEquals(120, film.getDuration(), "片长应2小时"),
() -> assertTrue(film.getBudget() > 50_000_000, "预算不足"),
() -> assertArrayEquals(new String[]{"动作", "冒险"}, film.getTags())
);
}
@Test
void cgiEffectTest() {
assumeTrue(System.getenv("ENABLE_CGI").equals("true"),
"需要CGI特效支持");
Scene scene = cgiStudio.renderDragonScene();
assertThat(scene.getFrames()).isGreaterThan(1000);
}
JUnit 5断言库选择:
@ParameterizedTest
@ValueSource(strings = {"导演", "制片", "编剧"})
void checkStaffRoles(String role) {
assertTrue(role.length() > 1, "职位名称过短");
}
@ParameterizedTest(name = "{0}的转换测试") // 自定义测试名称
@CsvSource({
"2023-01-01, 2023",
"2024-12-31, 2024",
"1999-07-28, 1999"
})
void yearExtractionTest(LocalDate date, int expectedYear) {
assertEquals(expectedYear, date.getYear());
}
@TestFactory
Stream<DynamicTest> dynamicFilmTests() {
return Stream.of("动作片", "爱情片", "科幻片")
.map(genre -> DynamicTest.dynamicTest("测试" + genre,
() -> assertTrue(filmService.validateGenre(genre))));
}
@ParameterizedTest
@MethodSource("provideAwardData")
void awardValidationTest(Award award) {
assertTrue(award.getYear() >= 1950, "年份异常");
}
private static Stream<Arguments> provideAwardData() {
return Stream.of(
Arguments.of(new Award("最佳影片", 2020)),
Arguments.of(new Award("最佳导演", 2021))
);
}
public class TimingExtension implements BeforeTestExecutionCallback,
AfterTestExecutionCallback {
private static final Logger LOG = LoggerFactory.getLogger(TimingExtension.class);
@Override
public void beforeTestExecution(ExtensionContext context) {
context.getStore(NAMESPACE).put("start", System.currentTimeMillis());
}
@Override
public void afterTestExecution(ExtensionContext context) {
long duration = System.currentTimeMillis() -
(long) context.getStore(NAMESPACE).get("start");
LOG.info("测试 {} 耗时 {}ms", context.getDisplayName(), duration);
}
}
// 使用示例
@ExtendWith(TimingExtension.class)
class PerformanceTest { ... }
// 差:
@Test void testCase1() {}
// 佳:采用Given-When-Then结构命名
@Test
void givenInvalidPassword_whenRegister_thenThrowException() {}
@Nested
@DisplayName("用户服务层测试")
class UserServiceLayerTest {
// 所有与用户相关的测试案例
}
@Nested
@DisplayName("订单服务层测试")
class OrderServiceLayerTest {
// 独立的订单业务测试
}
场景 | 推荐方案 | 反模式 |
---|---|---|
数据库操作 | @DataJpaTest | Mock全部Repository |
外部API调用 | MockWebServer | 硬编码测试数据 |
耗时操作 | @Timeout注解 | Thread.sleep() |
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-pluginartifactId>
<configuration>
<groups>critical-pathgroups>
<excludedGroups>slowexcludedGroups>
configuration>
plugin>
plugins>
build>
# 生成HTML测试报告
mvn test surefire-report:report
# 生成JUnit5的复古风格报告
mvn test -Djunit.platform.reporting.open.xml.enabled=true
掌握JUnit 5的你,现在已经成为单元测试的詹姆斯·卡梅隆!快去用这些技巧打造你的《阿凡达》级测试套件吧!记住,好的测试不仅防止BUG,更是活着的文档。当你的测试像电影剧本一样可读时,维护代码将变成一种享受。
(本文代码基于JUnit 5.9 + Java 17,推荐与AssertJ 3.24搭配使用)