junit-5-skill

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

JUnit 5 Testing Skill

JUnit 5 测试技能

You are a senior Java developer specializing in JUnit 5 testing.
你是一名专注于JUnit 5测试的资深Java开发人员。

Step 1 — Test Type

步骤1 — 测试类型

├─ "unit test", "assert" → Standard unit test
├─ "parameterized", "multiple inputs" → @ParameterizedTest
├─ "mock", "Mockito" → Unit test with Mockito
├─ "integration test", "Spring" → Read reference/spring-integration.md
└─ Default → Standard unit test
├─ "unit test", "assert" → 标准单元测试
├─ "parameterized", "multiple inputs" → @ParameterizedTest
├─ "mock", "Mockito" → 结合Mockito的单元测试
├─ "integration test", "Spring" → 参考reference/spring-integration.md
└─ 默认 → 标准单元测试

Core Patterns

核心模式

Basic Test

基础测试

java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    @DisplayName("Addition of two positive numbers")
    void addPositiveNumbers() {
        assertEquals(5, calculator.add(2, 3));
    }

    @Test
    void divideByZero_throwsException() {
        assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
    }

    @Test
    void multipleAssertions() {
        assertAll("calculator operations",
            () -> assertEquals(4, calculator.add(2, 2)),
            () -> assertEquals(0, calculator.subtract(2, 2)),
            () -> assertEquals(6, calculator.multiply(2, 3))
        );
    }
}
java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    @DisplayName("两个正数相加")
    void addPositiveNumbers() {
        assertEquals(5, calculator.add(2, 3));
    }

    @Test
    void divideByZero_throwsException() {
        assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
    }

    @Test
    void multipleAssertions() {
        assertAll("计算器操作",
            () -> assertEquals(4, calculator.add(2, 2)),
            () -> assertEquals(0, calculator.subtract(2, 2)),
            () -> assertEquals(6, calculator.multiply(2, 3))
        );
    }
}

Assertions Reference

断言参考

java
assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(object);
assertNotNull(object);
assertThrows(IllegalArgumentException.class, () -> service.process(null));
assertTimeout(Duration.ofSeconds(2), () -> service.longRunningOp());
assertAll("group",
    () -> assertNotNull(user.getName()),
    () -> assertTrue(user.getAge() > 0)
);
assertIterableEquals(List.of(1, 2, 3), actualList);
java
assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(object);
assertNotNull(object);
assertThrows(IllegalArgumentException.class, () -> service.process(null));
assertTimeout(Duration.ofSeconds(2), () -> service.longRunningOp());
assertAll("分组断言",
    () -> assertNotNull(user.getName()),
    () -> assertTrue(user.getAge() > 0)
);
assertIterableEquals(List.of(1, 2, 3), actualList);

Parameterized Tests

参数化测试

java
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "junit"})
void stringIsNotEmpty(String value) {
    assertFalse(value.isEmpty());
}

@ParameterizedTest
@CsvSource({"1,1,2", "2,3,5", "10,-5,5"})
void addNumbers(int a, int b, int expected) {
    assertEquals(expected, calculator.add(a, b));
}

@ParameterizedTest
@MethodSource("provideUsers")
void validateUser(String name, int age, boolean expected) {
    assertEquals(expected, validator.isValid(name, age));
}

static Stream<Arguments> provideUsers() {
    return Stream.of(
        Arguments.of("Alice", 25, true),
        Arguments.of("", 25, false),
        Arguments.of("Bob", -1, false)
    );
}

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {"  ", "\t"})
void blankStringsAreInvalid(String input) {
    assertFalse(validator.isValid(input));
}
java
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "junit"})
void stringIsNotEmpty(String value) {
    assertFalse(value.isEmpty());
}

@ParameterizedTest
@CsvSource({"1,1,2", "2,3,5", "10,-5,5"})
void addNumbers(int a, int b, int expected) {
    assertEquals(expected, calculator.add(a, b));
}

@ParameterizedTest
@MethodSource("provideUsers")
void validateUser(String name, int age, boolean expected) {
    assertEquals(expected, validator.isValid(name, age));
}

static Stream<Arguments> provideUsers() {
    return Stream.of(
        Arguments.of("Alice", 25, true),
        Arguments.of("", 25, false),
        Arguments.of("Bob", -1, false)
    );
}

@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {"  ", "\t"})
void blankStringsAreInvalid(String input) {
    assertFalse(validator.isValid(input));
}

Mocking with Mockito

Mockito模拟

java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock private UserRepository userRepo;
    @Mock private EmailService emailService;
    @InjectMocks private UserService userService;

    @Test
    void createUser_savesAndSendsEmail() {
        User user = new User("alice@test.com", "Alice");
        when(userRepo.save(any(User.class))).thenReturn(user);

        User result = userService.createUser("alice@test.com", "Alice");

        assertNotNull(result);
        verify(userRepo).save(any(User.class));
        verify(emailService).sendWelcomeEmail("alice@test.com");
    }

    @Test
    void getUser_notFound_throwsException() {
        when(userRepo.findById(99L)).thenReturn(Optional.empty());
        assertThrows(UserNotFoundException.class, () -> userService.getUser(99L));
    }
}
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock private UserRepository userRepo;
    @Mock private EmailService emailService;
    @InjectMocks private UserService userService;

    @Test
    void createUser_savesAndSendsEmail() {
        User user = new User("alice@test.com", "Alice");
        when(userRepo.save(any(User.class))).thenReturn(user);

        User result = userService.createUser("alice@test.com", "Alice");

        assertNotNull(result);
        verify(userRepo).save(any(User.class));
        verify(emailService).sendWelcomeEmail("alice@test.com");
    }

    @Test
    void getUser_notFound_throwsException() {
        when(userRepo.findById(99L)).thenReturn(Optional.empty());
        assertThrows(UserNotFoundException.class, () -> userService.getUser(99L));
    }
}

Nested Tests

嵌套测试

java
@DisplayName("UserService")
class UserServiceTest {
    @Nested
    @DisplayName("when creating a user")
    class CreateUser {
        @Test void withValidData_succeeds() { }
        @Test void withDuplicateEmail_throwsException() { }
    }

    @Nested
    @DisplayName("when deleting a user")
    class DeleteUser {
        @Test void existingUser_removesFromDb() { }
        @Test void nonExistentUser_throwsException() { }
    }
}
java
@DisplayName("UserService")
class UserServiceTest {
    @Nested
    @DisplayName("创建用户时")
    class CreateUser {
        @Test void withValidData_succeeds() { }
        @Test void withDuplicateEmail_throwsException() { }
    }

    @Nested
    @DisplayName("删除用户时")
    class DeleteUser {
        @Test void existingUser_removesFromDb() { }
        @Test void nonExistentUser_throwsException() { }
    }
}

Anti-Patterns

反模式

BadGoodWhy
@Test public void test1()
@Test void shouldCalculateSum()
Descriptive names
Testing private methodsTest via public APIImplementation detail
No @DisplayNameAlways add display namesBetter reporting
assertEquals(true, x)
assertTrue(x)
More readable
不良做法良好做法原因
@Test public void test1()
@Test void shouldCalculateSum()
命名更具描述性
测试私有方法通过公共API测试私有方法属于实现细节
不使用@DisplayName始终添加显示名称测试报告更清晰
assertEquals(true, x)
assertTrue(x)
可读性更强

Maven Dependencies

Maven依赖

xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.14.0</version>
    <scope>test</scope>
</dependency>
xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.14.0</version>
    <scope>test</scope>
</dependency>

Quick Reference

快速参考

TaskCommand
Run all
mvn test
or
./gradlew test
Run class
mvn test -Dtest=UserServiceTest
Run method
mvn test -Dtest=UserServiceTest#createUser_succeeds
Run tagged
@Tag("slow")
+
mvn test -Dgroups="slow"
Disable
@Disabled("Reason")
Conditional
@EnabledOnOs(OS.LINUX)
Timeout
@Timeout(value = 5, unit = TimeUnit.SECONDS)
Repeated
@RepeatedTest(5)
Order
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
任务命令
运行全部测试
mvn test
./gradlew test
运行指定类
mvn test -Dtest=UserServiceTest
运行指定方法
mvn test -Dtest=UserServiceTest#createUser_succeeds
运行指定标签测试
@Tag("slow")
+
mvn test -Dgroups="slow"
禁用测试
@Disabled("Reason")
条件启用
@EnabledOnOs(OS.LINUX)
超时设置
@Timeout(value = 5, unit = TimeUnit.SECONDS)
重复测试
@RepeatedTest(5)
测试排序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)

Deep Patterns

深度模式

For production-grade patterns, see
reference/playbook.md
:
SectionWhat's Inside
§1 Project SetupMaven deps, parallel config, surefire
§2 Test LifecycleBeforeAll/Each, ordering, tags
§3 ParameterizedCsvSource, MethodSource, EnumSource, ValueSource
§4 Mockito@Mock/@InjectMocks, captor, verify order
§5 Nested & Dynamic@Nested grouping, @TestFactory
§6 AssertJFluent assertions, extracting, collection checks
§7 Conditional@EnabledOnOs, assumptions, @EnabledIf
§8 Custom ExtensionsTiming, retry, BeforeTestExecution
§9 CI/CDGitHub Actions with test reporter
§10 Debugging Table8 common problems with fixes
§11 Best Practices12-item production checklist
如需生产级模式,请查看
reference/playbook.md
章节内容
§1 项目配置Maven依赖、并行配置、surefire插件
§2 测试生命周期BeforeAll/Each、排序、标签
§3 参数化测试CsvSource、MethodSource、EnumSource、ValueSource
§4 Mockito@Mock/@InjectMocks、参数捕获器、验证顺序
§5 嵌套与动态测试@Nested分组、@TestFactory
§6 AssertJ流式断言、提取操作、集合检查
§7 条件测试@EnabledOnOs、假设条件、@EnabledIf
§8 自定义扩展计时、重试、BeforeTestExecution
§9 CI/CD集成GitHub Actions测试报告
§10 调试指南8个常见问题及修复方案
§11 最佳实践12项生产环境检查清单