1. JUnit 5 介绍
JUnit 5 由 JUnit Platform、JUnit Jupiter、JUnit Vintage 组成:
- JUnit Platform:运行测试的底层平台
- JUnit Jupiter:JUnit 5 的新 API
- JUnit Vintage:兼容 JUnit 4/3 测试
JUnit 5 vs. JUnit 4
- JUnit 5 更灵活,支持 Lambda、动态测试、扩展 API
- JUnit 4 结构简单,但不如 JUnit 5 强大
2. 依赖配置
在 Gradle 中引入 JUnit 5:
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.9.3")
}
3. JUnit 5 主要注解
注解 | 作用 |
---|---|
@Test | 标记一个测试方法 |
@BeforeEach | 每个测试方法执行前运行 |
@AfterEach | 每个测试方法执行后运行 |
@BeforeAll | 在所有测试前执行(静态方法) |
@AfterAll | 在所有测试后执行(静态方法) |
@DisplayName | 自定义测试名称 |
@Disabled | 禁用测试 |
@ParameterizedTest | 参数化测试 |
@ExtendWith | 扩展 JUnit 功能 |
@Nested | 组织嵌套测试类 |
@Tag | 给测试分组 |
@TestInstance | 变更测试实例生命周期 |
4. 基础用法
(1)基本测试
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class ExampleTest {
@Test
fun `addition should be correct`() {
val result = 3 + 5
assertEquals(8, result)
}
}
✅ JUnit 5 使用 assertEquals
断言,更简洁
✅ 支持 Kotlin 反引号方法名,更直观
(2)测试生命周期
import org.junit.jupiter.api.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LifecycleTest {
@BeforeAll
fun setupAll() {
println("在所有测试前执行")
}
@BeforeEach
fun setupEach() {
println("在每个测试前执行")
}
@Test
fun test1() {
println("执行 test1")
}
@Test
fun test2() {
println("执行 test2")
}
@AfterEach
fun tearDownEach() {
println("在每个测试后执行")
}
@AfterAll
fun tearDownAll() {
println("在所有测试后执行")
}
}
✅ @BeforeAll
/ @AfterAll
适用于 初始化资源(如数据库连接)
✅ @BeforeEach
/ @AfterEach
适用于 每个测试前后的清理
(3)跳过测试
@Test
@Disabled("暂时禁用")
fun ignoredTest() {
println("不会执行")
}
✅ @Disabled
可以 临时跳过 失败的测试
5. 断言(Assertions)
JUnit 5 提供了 Assertions
类:
import org.junit.jupiter.api.Assertions.*
@Test
fun `test assertions`() {
assertEquals(10, 5 + 5, "计算结果错误")
assertTrue(5 > 2, "条件应为真")
assertFalse(5 < 2, "条件应为假")
assertNotNull("Hello", "对象不应为空")
}
✅ 支持多个断言,提供错误提示信息
(1)组合断言(assertAll
)
@Test
fun `test multiple assertions`() {
assertAll(
{ assertEquals(2, 1 + 1) },
{ assertTrue("hello".startsWith("h")) },
{ assertFalse("hello".endsWith("z")) }
)
}
✅ 多个断言同时执行,避免一个失败后跳过其他检查
(2)异常断言
@Test
fun `test exception`() {
val exception = assertThrows<IllegalArgumentException> {
throw IllegalArgumentException("Invalid input")
}
assertEquals("Invalid input", exception.message)
}
✅ 检测方法是否抛出指定异常
6. 参数化测试
(1)单个参数
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
class ParameterizedExampleTest {
@ParameterizedTest
@ValueSource(strings = ["hello", "world"])
fun `test with multiple values`(input: String) {
assertTrue(input.length > 3)
}
}
✅ 一次性测试多个输入值,避免重复代码
(2)多个参数
import org.junit.jupiter.params.provider.CsvSource
@ParameterizedTest
@CsvSource("2,3,5", "3,5,8", "6,7,13")
fun `test addition`(a: Int, b: Int, expected: Int) {
assertEquals(expected, a + b)
}
✅ CsvSource
允许多个参数传递
7. 嵌套测试
JUnit 5 支持嵌套测试:
import org.junit.jupiter.api.Nested
class OuterTest {
@Test
fun `outer test`() {
println("外部测试")
}
@Nested
inner class InnerTest {
@Test
fun `inner test`() {
println("内部测试")
}
}
}
✅ 组织复杂测试,清晰分层
8. 测试超时
import org.junit.jupiter.api.Timeout
import java.util.concurrent.TimeUnit
@Test
@Timeout(value = 2, unit = TimeUnit.SECONDS)
fun `test should finish within 2 seconds`() {
Thread.sleep(1000) // 1 秒,未超时
}
✅ 超时限制防止测试卡死
9. 扩展 JUnit
(1)自定义扩展
import org.junit.jupiter.api.extension.*
class MyExtension : BeforeTestExecutionCallback {
override fun beforeTestExecution(context: ExtensionContext?) {
println("测试开始前执行")
}
}
@ExtendWith(MyExtension::class)
class ExtensionTest {
@Test
fun `test with custom extension`() {
println("测试运行中")
}
}
✅ 扩展 JUnit,添加自定义行为
10. 运行测试
# 运行所有测试
./gradlew test
# 运行指定测试
./gradlew test --tests "com.example.MyTest"
# 生成测试报告
./gradlew jacocoTestReport
总结
功能 | JUnit 5 方式 |
---|---|
基本测试 | @Test |
生命周期管理 | @BeforeEach 、@AfterEach |
跳过测试 | @Disabled |
断言 | assertEquals 、assertAll |
参数化测试 | @ParameterizedTest |
嵌套测试 | @Nested |
超时 | @Timeout |
自定义扩展 | @ExtendWith |
使用 JUnit 5,可以写出 更高效、可读性更强的测试 🚀