JUnit5全面技术实践指南 1. JUnit框架概述 1.1 发展历史与版本演进 JUnit作为Java生态中最具影响力的测试框架,其发展历程见证了Java测试技术的演进:
版本
发布年份
核心特性
技术突破
JUnit 3
2002
基于反射和命名约定
引入测试用例概念
JUnit 4
2006
注解驱动测试
@Test注解革命
JUnit 5
2017
模块化架构
Jupiter引擎、Lambda支持
JUnit 5架构革新 : JUnit 5采用了全新的模块化设计,由三个主要子项目组成:
JUnit Platform :测试引擎基础架构,支持多种测试框架
JUnit Jupiter :JUnit 5的核心编程模型和扩展模型
JUnit Vintage :向后兼容JUnit 3和4的测试引擎
1.2 在Java测试生态中的定位 JUnit在Java测试生态系统中扮演着核心角色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 graph TD A[Java测试生态] --> B[单元测试层] A --> C[集成测试层] A --> D[端到端测试层] B --> B1[JUnit5 - 核心引擎] B --> B2[TestNG - 并行测试] C --> C1[Spring Test - 集成测试] C --> C2[DBUnit - 数据库测试] D --> D1[Selenium - Web UI测试] D --> D2[REST Assured - API测试] style B1 fill:#ff9999 style B2 fill:#ffcc99
1.3 核心设计理念与优势 核心设计理念 :
约定优于配置 :智能默认值,减少样板代码
可扩展性 :插件式架构,支持自定义扩展
现代Java支持 :充分利用Java 8+特性
IDE友好 :与主流IDE深度集成
技术优势 :
Lambda表达式支持 :简化测试代码
动态测试生成 :运行时创建测试用例
嵌套测试结构 :更好的测试组织
条件化测试执行 :基于环境的智能测试
并行执行 :提升测试效率
2. 环境配置指南 2.1 依赖管理配置 Maven配置 JUnit 5.10.0基础配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <junit.version > 5.10.0</junit.version > </properties > <dependencies > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-engine</artifactId > <version > ${junit.version}</version > <scope > test</scope > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-api</artifactId > <version > ${junit.version}</version > <scope > test</scope > </dependency > <dependency > <groupId > org.junit.jupiter</groupId > <artifactId > junit-jupiter-params</artifactId > <version > ${junit.version}</version > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <version > 3.2.2</version > <configuration > <includes > <include > **/*Test.java</include > <include > **/*Tests.java</include > </includes > <parallel > methods</parallel > <threadCount > 4</threadCount > </configuration > </plugin > </plugins > </build >
Gradle配置 Gradle 8.x配置示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 plugins { id 'java' id 'org.springframework.boot' version '3.2.0' } java { sourceCompatibility = JavaVersion.VERSION_17 } dependencies { testImplementation(platform('org.junit:junit-bom:5.10.0' )) testImplementation('org.junit.jupiter:junit-jupiter' ) testImplementation('org.junit.jupiter:junit-jupiter-params' ) testImplementation('org.mockito:mockito-core:5.7.0' ) testImplementation('org.mockito:mockito-junit-jupiter:5.7.0' ) testImplementation('org.assertj:assertj-core:3.24.2' ) } test { useJUnitPlatform() maxParallelForks = Runtime .runtime .availableProcessors() testLogging { events "passed" , "skipped" , "failed" } }
2.2 IDE集成方案 IntelliJ IDEA配置 智能测试配置 :
自动测试发现 :IDEA自动识别@Test注解
测试运行配置 :
1 2 3 4 Run → Edit Configurations → JUnit - VM options: -ea -Djava.util.logging.config.file=logging.properties - Working directory: $MODULE_DIR$ - Environment variables: SPRING_PROFILES_ACTIVE=test
代码模板配置 :
1 2 3 4 5 6 7 8 9 10 11 @org .junit.jupiter.api.Testvoid ${NAME}() { ${BODY} org.assertj.core.api.Assertions.assertThat(${RESULT}).isEqualTo(${EXPECTED}); }
Eclipse配置 Eclipse 2023-09+配置 :
1 2 <classpathentry kind ="con" path ="org.eclipse.jdt.junit.JUNIT_CONTAINER/5" />
2.3 基础环境要求
组件
最低版本
推荐版本
说明
JDK
8
17+
Lambda表达式支持
Maven
3.6.0
3.9.0+
插件兼容性
Gradle
6.8
8.4+
Kotlin DSL支持
IDE
IntelliJ 2020.3
2023.2+
完整特性支持
3. 核心功能详解 3.1 测试用例编写规范 基础测试结构 标准测试类模板 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.example.service;import org.junit.jupiter.api.*;import static org.junit.jupiter.api.Assertions.*;@DisplayName("用户服务测试") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class UserServiceTest { private UserService userService; @BeforeEach void setUp () { userService = new UserService (); } @Test @Order(1) @DisplayName("应该成功创建用户") void shouldCreateUserSuccessfully () { User user = new User ("john.doe" , "john@example.com" ); User createdUser = userService.createUser(user); assertNotNull(createdUser.getId(), "用户ID不应为空" ); assertEquals("john.doe" , createdUser.getUsername()); } @Nested @DisplayName("用户验证测试") class UserValidationTest { @Test @DisplayName("应该拒绝无效邮箱格式") void shouldRejectInvalidEmail () { assertThrows(IllegalArgumentException.class, () -> userService.createUser(new User ("test" , "invalid-email" ))); } } }
3.2 断言机制解析 核心断言方法 JUnit 5断言分类 :
断言类型
方法示例
使用场景
布尔断言
assertTrue()
, assertFalse()
条件验证
相等断言
assertEquals()
, assertNotEquals()
值比较
空值断言
assertNull()
, assertNotNull()
空值检查
异常断言
assertThrows()
, assertDoesNotThrow()
异常验证
超时断言
assertTimeout()
, assertTimeoutPreemptively()
性能测试
高级断言示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import static org.junit.jupiter.api.Assertions.*;class AssertionExamplesTest { @Test void demonstrateAdvancedAssertions () { String actual = "Hello, JUnit 5!" ; assertAll("字符串验证" , () -> assertNotNull(actual), () -> assertTrue(actual.startsWith("Hello" )), () -> assertTrue(actual.endsWith("!" )), () -> assertEquals(15 , actual.length()) ); IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> validateAge(-5 ) ); assertEquals("年龄不能为负数" , exception.getMessage()); assertTimeout(Duration.ofSeconds(1 ), () -> { Thread.sleep(500 ); return "操作完成" ; }); } private void validateAge (int age) { if (age < 0 ) { throw new IllegalArgumentException ("年龄不能为负数" ); } } }
3.3 生命周期注解详解 完整的生命周期钩子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @TestInstance(TestInstance.Lifecycle.PER_CLASS) class LifecycleDemoTest { private static int instanceCount = 0 ; private int counter = 0 ; public LifecycleDemoTest () { instanceCount++; System.out.println("构造函数调用,实例数量: " + instanceCount); } @BeforeAll static void globalSetup () { System.out.println("@BeforeAll: 全局初始化 - 仅执行一次" ); } @AfterAll static void globalTearDown () { System.out.println("@AfterAll: 全局清理 - 仅执行一次" ); } @BeforeEach void setUp () { counter++; System.out.println("@BeforeEach: 测试前准备 - 实例: " + instanceCount + ", 计数器: " + counter); } @AfterEach void tearDown () { System.out.println("@AfterEach: 测试后清理 - 计数器: " + counter); } @Test void firstTest () { System.out.println("执行第一个测试" ); } @Test void secondTest () { System.out.println("执行第二个测试" ); } }
3.4 参数化测试实现 多种参数化方式 @ValueSource示例 :
1 2 3 4 5 6 7 8 9 10 11 @ParameterizedTest @ValueSource(strings = {"racecar", "radar", "level"}) void testPalindromeStrings (String word) { assertTrue(isPalindrome(word)); } @ParameterizedTest @ValueSource(ints = {1, 3, 5, 7, 9}) void testOddNumbers (int number) { assertTrue(number % 2 != 0 ); }
@CsvSource复杂参数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @ParameterizedTest @CsvSource({ "2, 3, 5", "7, 5, 12", "10, 15, 25" }) void testAddition (int a, int b, int expectedSum) { assertEquals(expectedSum, a + b); } @ParameterizedTest @CsvSource({ "admin, true", "user, false", "guest, false" }) void testUserPermissions (String username, boolean shouldHaveAccess) { assertEquals(shouldHaveAccess, hasAdminAccess(username)); }
@MethodSource动态参数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 @ParameterizedTest @MethodSource("provideComplexTestData") void testWithComplexObjects (User user, String expectedRole) { assertEquals(expectedRole, userService.determineRole(user)); } static Stream<Arguments> provideComplexTestData () { return Stream.of( Arguments.of(new User ("admin" , "admin@company.com" , 30 ), "ADMIN" ), Arguments.of(new User ("john" , "john@company.com" , 25 ), "USER" ), Arguments.of(new User ("guest" , "guest@temp.com" , 18 ), "GUEST" ) ); }
3.5 异常测试方法 异常测试最佳实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class ExceptionTestingDemo { @Test void testExceptionTypeAndMessage () { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> calculator.divide(10 , 0 ) ); assertEquals("除数不能为零" , exception.getMessage()); } @Test void testExceptionWithCustomMatcher () { Exception exception = assertThrows( Exception.class, () -> fileProcessor.readFile("nonexistent.txt" ) ); assertThat(exception.getMessage()).contains("文件未找到" ); } @Test void testNoExceptionScenario () { assertDoesNotThrow(() -> calculator.add(5 , 3 )); } }
4. 高级特性应用 4.1 测试套件组织 分层测试架构 测试套件配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import org.junit.platform.suite.api.*;@Suite @SelectPackages("com.example.service") @IncludeClassNamePatterns(".*Test") @ExcludeTags("integration") public class UnitTestSuite { } @Suite @SelectClasses({UserServiceTest.class, OrderServiceTest.class}) @IncludeTags("fast") public class ServiceTestSuite { } @Suite @SelectPackages("com.example") @IncludeTags("integration") @ExcludeTags("slow") public class IntegrationTestSuite { }
4.2 规则(Rule)扩展机制 JUnit 5扩展模型 自定义扩展实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import org.junit.jupiter.api.extension.*;public class DatabaseExtension implements BeforeAllCallback , AfterAllCallback { @Override public void beforeAll (ExtensionContext context) { System.out.println("初始化测试数据库" ); DatabaseManager.startEmbeddedDatabase(); } @Override public void afterAll (ExtensionContext context) { System.out.println("清理测试数据库" ); DatabaseManager.stopEmbeddedDatabase(); } } @ExtendWith(DatabaseExtension.class) class UserRepositoryTest { @Test void shouldSaveUserToDatabase () { } }
条件化测试扩展 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DisabledOnWeekendCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition (ExtensionContext context) { DayOfWeek currentDay = LocalDate.now().getDayOfWeek(); if (currentDay == DayOfWeek.SATURDAY || currentDay == DayOfWeek.SUNDAY) { return ConditionEvaluationResult.disabled("周末禁用测试" ); } return ConditionEvaluationResult.enabled("工作日启用测试" ); } } @ExtendWith(DisabledOnWeekendCondition.class) class BusinessLogicTest { }
4.3 动态测试实现 运行时测试生成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class DynamicTestDemo { @TestFactory Stream<DynamicTest> generateMathTests () { return Stream.of( dynamicTest("2 + 3 = 5" , () -> assertEquals(5 , 2 + 3 )), dynamicTest("7 - 2 = 5" , () -> assertEquals(5 , 7 - 2 )), dynamicTest("3 * 4 = 12" , () -> assertEquals(12 , 3 * 4 )) ); } @TestFactory Stream<DynamicTest> generateFileProcessingTests () { return Files.list(Paths.get("test-data" )) .filter(path -> path.toString().endsWith(".json" )) .map(path -> dynamicTest( "处理文件: " + path.getFileName(), () -> processJsonFile(path) )); } @TestFactory Iterable<DynamicTest> generateParameterizedTests () { return Arrays.asList( dynamicContainer("算术运算测试" , Arrays.asList( dynamicTest("加法" , () -> assertEquals(4 , 2 + 2 )), dynamicTest("减法" , () -> assertEquals(2 , 4 - 2 )) )), dynamicContainer("字符串测试" , Arrays.asList( dynamicTest("长度" , () -> assertEquals(5 , "hello" .length())), dynamicTest("转换" , () -> assertEquals("HELLO" , "hello" .toUpperCase())) )) ); } }
4.4 与Mock框架集成 Mockito集成方案 基础Mock配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @Mock private EmailService emailService; @InjectMocks private UserService userService; @Test void shouldCreateUserWithMockedDependencies () { User newUser = new User ("test@example.com" , "password123" ); when (userRepository.save(any(User.class))).thenAnswer(invocation -> { User savedUser = invocation.getArgument(0 ); savedUser.setId(1L ); return savedUser; }); doNothing().when (emailService).sendWelcomeEmail(anyString()); User createdUser = userService.registerUser(newUser); assertNotNull(createdUser.getId()); assertEquals("test@example.com" , createdUser.getEmail()); verify(userRepository).save(newUser); verify(emailService).sendWelcomeEmail("test@example.com" ); } @Test void shouldHandleRepositoryException () { when (userRepository.save(any(User.class))) .thenThrow(new DataAccessException ("数据库错误" )); assertThrows(ServiceException.class, () -> { userService.registerUser(new User ("test@example.com" , "password" )); }); } }
高级Mock场景 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @ExtendWith(MockitoExtension.class) class AdvancedMockingTest { @Mock private HttpClient httpClient; @InjectMocks private WeatherService weatherService; @Test void testWeatherApiWithMockedResponse () throws IOException { HttpResponse mockResponse = mock(HttpResponse.class); when (mockResponse.getStatusCode()).thenReturn(200 ); when (mockResponse.getBody()).thenReturn("{" temperature": 25.5, " humidity": 60}" ); when (httpClient.get(anyString())).thenReturn(mockResponse); WeatherData data = weatherService.getCurrentWeather("Beijing" ); assertEquals(25.5 , data.getTemperature()); assertEquals(60 , data.getHumidity()); } }
5. 最佳实践建议 5.1 测试代码组织结构 标准项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 src/ ├── main/ │ ├── java/ │ │ └── com/example/ │ │ ├── service/ │ │ │ ├── UserService.java │ │ │ └── OrderService.java │ │ └── model/ │ └── resources/ └── test/ ├── java/ │ └── com/example/ │ ├── service/ │ │ ├── UserServiceTest.java │ │ └── OrderServiceTest.java │ ├── integration/ │ │ └── UserServiceIntegrationTest.java │ └── fixtures/ │ └── TestDataFactory.java └── resources/ ├── application-test.yml └── test-data/ ├── users.json └── orders.json
命名约定 :
测试类:{被测试类}Test
或 {被测试类}Tests
集成测试:{被测试类}IntegrationTest
测试方法:should{预期行为}When{条件}
测试数据:test{数据类型}
5.2 可维护性设计原则 测试代码质量原则 单一职责原则 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class UserServiceTest { @Test void shouldActivateUserWhenEmailIsValid () { } @Test void shouldSendActivationEmailWhenUserRegistered () { } @Test void testUserRegistrationAndActivationAndEmailSending () { } }
测试数据工厂模式 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TestDataFactory { public static User createValidUser () { return User.builder() .id(1L ) .username("testuser" ) .email("test@example.com" ) .password("hashedPassword" ) .createdAt(LocalDateTime.now()) .build(); } public static User createAdminUser () { return createValidUser().toBuilder() .role(UserRole.ADMIN) .permissions(Set.of("READ" , "WRITE" , "DELETE" )) .build(); } public static List<User> createUserList (int count) { return IntStream.range(0 , count) .mapToObj(i -> createValidUser().toBuilder() .id((long ) i) .username("user" + i) .build()) .collect(Collectors.toList()); } }
5.3 性能优化技巧 并行测试配置 Maven并行测试 :
1 2 3 4 5 6 7 8 9 10 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <configuration > <parallel > all</parallel > <threadCount > 4</threadCount > <perCoreThreadCount > true</perCoreThreadCount > <parallelOptimized > true</parallelOptimized > </configuration > </plugin >
Gradle并行测试 :
1 2 3 4 5 6 7 8 test { maxParallelForks = Runtime .runtime .availableProcessors().intdiv(2 ) ?: 1 systemProperty 'junit.jupiter.execution.parallel.enabled' , 'true' systemProperty 'junit.jupiter.execution.parallel.mode.default' , 'concurrent' systemProperty 'junit.jupiter.execution.parallel.mode.classes.default' , 'concurrent' }
测试执行策略 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @TestMethodOrder(OrderAnnotation.class) class OptimizedTestSuite { @Test @Order(1) @Tag("fast") void fastUnitTest () { } @Test @Order(2) @Tag("slow") @DisabledIfEnvironmentVariable(named = "CI", matches = "true") void slowIntegrationTest () { } }
5.4 持续集成方案 GitHub Actions配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 name: Java CI with Maven on: push: branches: [ main , develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest strategy: matrix: java: [11 , 17 , 21 ] steps: - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' cache: maven - name: Run tests run: mvn -B test --file pom.xml - name: Generate test report uses: dorny/test-reporter@v1 if: success() || failure() with: name: Maven Tests path: target/surefire-reports/*.xml reporter: java-junit - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./target/site/jacoco/jacoco.xml flags: unittests name: codecov-umbrella
6. 常见问题排查 6.1 典型错误场景分析 测试发现失败 问题症状 :测试类不被识别
解决方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.example.service;public class UserServiceTest { @Test public void testMethod () { } } <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>** *Tests.java</include> </includes> </configuration> </plugin> </plugins> </build>
依赖注入问题 Spring Boot测试配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @SpringBootTest @AutoConfigureMockMvc @TestPropertySource(locations = "classpath:application-test.properties") class UserControllerIntegrationTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void shouldReturnUserWhenExists () throws Exception { when (userService.getUserById(1L )) .thenReturn(Optional.of(new User (1L , "test@example.com" ))); mockMvc.perform(get("/api/users/1" )) .andExpect(status().isOk()) .andExpect(jsonPath("$.email" ).value("test@example.com" )); } }
6.2 调试技巧 IDE调试配置 断点调试技巧 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class DebuggableTest { @Test void debugComplexLogic () { Logger logger = LoggerFactory.getLogger(DebuggableTest.class); int result = complexCalculation(10 , 5 ); logger.debug("计算结果: {}" , result); assertEquals(50 , result, "复杂计算结果不正确" ); } @Test void debugWithTemporaryAssertions () { String intermediate = processStep1("input" ); assertNotNull(intermediate, "步骤1返回null" ); String finalResult = processStep2(intermediate); assertTrue(finalResult.contains("expected" ), "步骤2结果不符合预期" ); } }
6.3 测试覆盖率提升方法 JaCoCo配置示例 Maven JaCoCo配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <plugin > <groupId > org.jacoco</groupId > <artifactId > jacoco-maven-plugin</artifactId > <version > 0.8.10</version > <configuration > <excludes > <exclude > **/model/**</exclude > <exclude > **/config/**</exclude > </excludes > </configuration > <executions > <execution > <goals > <goal > prepare-agent</goal > </goals > </execution > <execution > <id > report</id > <phase > test</phase > <goals > <goal > report</goal > </goals > </execution > </executions > </plugin >
覆盖率报告分析 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class CoverageImprovementTest { @Test void testEdgeCases () { assertEquals(0 , calculator.divide(0 , 5 )); assertEquals(0 , calculator.divide(5 , 0 )); } @ParameterizedTest @CsvSource({"1,1,1", "2,1,2", "3,1,3"}) void testMultipleScenarios (int a, int b, int expected) { assertEquals(expected, calculator.divide(a, b)); } }
7. 版本差异标注 7.1 JUnit 4 vs JUnit 5关键差异
特性
JUnit 4
JUnit 5
注解
@Test
@Test
生命周期
@Before
, @After
@BeforeEach
, @AfterEach
类级生命周期
@BeforeClass
, @AfterClass
@BeforeAll
, @AfterAll
断言
Assert.assertEquals
Assertions.assertEquals
假设
Assume.assumeTrue
Assumptions.assumeTrue
参数化
@RunWith(Parameterized.class)
@ParameterizedTest
规则
@Rule
, @ClassRule
扩展模型 @ExtendWith
动态测试
不支持
@TestFactory
并行执行
有限支持
原生支持
7.2 迁移指南 逐步迁移策略 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RunWith(JUnitPlatform.class) @SelectPackages("com.example") public class JUnit4MigrationSuite { } class NewJUnit5Test { @Test void newTest () { } }
8. 延伸学习资源 8.1 官方资源
8.2 进阶书籍
《JUnit实战》 - Petar Tahchiev等
《Java测试驱动开发》 - Lasse Koskela
《Effective Unit Testing》 - Lasse Koskela
8.3 在线课程
JUnit 5深度解析 (Pluralsight)
Java单元测试最佳实践 (Udemy)
测试驱动开发实战 (Coursera)
8.4 工具集成
IntelliJ IDEA : 内置JUnit 5支持
Eclipse : JUnit 5插件
VS Code : Java Extension Pack
Maven : Surefire插件
Gradle : JUnit Platform插件
本文档基于JUnit 5.10.0版本编写,适用于Java 8及以上版本。建议定期查看官方文档获取最新更新。