1. 简介

Junit 5 是 Java 生态中主流单元测试框架的最新版本。它深度集成了 Java 8 的特性,尤其是围绕 Lambda 表达式 构建了大量新功能。

本文聚焦于 JUnit 5 与 Kotlin 的集成表现。对于使用 Kotlin 的开发者来说,好消息是:绝大多数特性都能无缝衔接,甚至在某些场景下语法更简洁、可读性更强。

2. 基础 JUnit 5 测试

用 Kotlin 编写的 JUnit 5 测试类与 Java 几乎一致:定义测试类,使用 @Test 注解标记方法,编写断言逻辑即可。

class CalculatorTest {
    private val calculator = Calculator()

    @Test
    fun whenAdding1and3_thenAnswerIs4() {
        Assertions.assertEquals(4, calculator.add(1, 3))
    }
}

✅ 所有标准注解开箱即用:

  • @Test
  • @BeforeAll / @BeforeEach
  • @AfterEach / @AfterAll

⚠️ 注意:JUnit 5 统一使用 Assertions 类进行断言(不再是 JUnit 4 的 Assert),这是通用变更,与 Kotlin 无关。

使用反引号命名提升可读性

Kotlin 允许使用反引号(backticks)包裹函数名,这非常适合编写“自然语言风格”的测试用例名称:

@Test
fun `Adding 1 and 3 should be equal to 4`() {
    Assertions.assertEquals(4, calculator.add(1, 3))
}

✅ 优势:大幅提升测试意图的可读性
❌ 注意:仅建议在测试代码中使用,生产代码避免滥用反引号命名

3. 高级断言功能

JUnit 5 提供了基于 Lambda 的高级断言机制,在 Kotlin 中使用时语法更优雅。

3.1 异常断言

验证某个调用是否抛出预期异常,并可进一步检查异常属性:

@Test
fun `Dividing by zero should throw the DivideByZeroException`() {
    val exception = Assertions.assertThrows(DivideByZeroException::class.java) {
        calculator.divide(5, 0)
    }

    Assertions.assertEquals(5, exception.numerator)
}

✅ Kotlin 优势:直接传入代码块 {},无需显式声明 lambda,比 Java 的 (()-> ...) 更简洁。

3.2 多重断言(assertAll)

一次性执行多个断言,即使前面失败也会继续执行后续断言,便于一次性发现多个问题:

@Test
fun `The square of a number should be equal to that number multiplied in itself`() {
    Assertions.assertAll(
        Executable { Assertions.assertEquals(1, calculator.square(1)) },
        Executable { Assertions.assertEquals(4, calculator.square(2)) },
        Executable { Assertions.assertEquals(9, calculator.square(3)) }
    )
}

⚠️ 踩坑提醒:Kotlin 不支持自动将 lambda 转为 Executable 函数式接口,必须手动包装成 Executable { },否则编译报错。

3.3 布尔值断言(Supplier 支持)

可传入 lambda 或方法引用作为布尔值来源:

@Test
fun `isEmpty should return true for empty lists`() {
    val list = listOf<String>()
    Assertions.assertTrue(list::isEmpty)
}

✅ 实际价值:结合方法引用(如 list::isEmpty)可写出非常简洁的断言。

3.4 延迟计算的失败消息

当断言失败时才计算错误消息,避免不必要的字符串拼接开销:

@Test
fun `3 is equal to 4`() {
    Assertions.assertEquals(3, 4) {
        "Three does not equal four"
    }
}

✅ 性能提示:适用于消息内容需复杂计算的场景,能有效提升大规模测试套件的执行速度。

4. 数据驱动测试

JUnit 5 原生支持数据驱动测试,Kotlin 的集合操作让这类测试更易维护。

4.1 TestFactory 方法

使用 @TestFactory 返回动态测试集合:

@TestFactory
fun testSquares() = listOf(
    1 to 1,
    2 to 4,
    3 to 9,
    4 to 16,
    5 to 25
).map { (input, expected) ->
    DynamicTest.dynamicTest("when I calculate $input^2 then I get $expected") {
        Assertions.assertEquals(expected, calculator.square(input))
    }
}

✅ 最佳实践:将测试数据提取为共用字段,实现跨测试复用:

private val squaresTestData = listOf(
    1 to 1,
    2 to 4,
    3 to 9,
    4 to 16,
    5 to 25
)

@TestFactory
fun testSquares() = squaresTestData.map { /* ... */ }

@TestFactory
fun testSquareRoots() = squaresTestData.map { (expected, input) -> /* ... */ }

4.2 参数化测试(Parameterized Tests)

需引入额外依赖:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.9.3</version>
</dependency>

最新版本见 Maven Central

使用 @MethodSource

在 Kotlin 中需配合 companion object@JvmStatic

@ParameterizedTest
@MethodSource("squares")
fun testSquares(input: Int, expected: Int) {
    Assertions.assertEquals(expected, input * input)
}

companion object {
    @JvmStatic
    fun squares() = listOf(
        Arguments.of(1, 1),
        Arguments.of(2, 4)
    )
}

⚠️ 局限:每个类只能有一个 companion object,参数生成方法需集中管理。

推荐:使用 @CsvSource(更简洁)

对于简单数据,CSV 形式更直观:

@ParameterizedTest
@CsvSource(
    "1, 1",
    "2, 4",
    "3, 9"
)
fun testSquares(input: Int, expected: Int) {
    Assertions.assertEquals(expected, input * input)
}

✅ 场景建议:小规模、结构简单的测试数据优先选 @CsvSource

5. 标记测试(Tagged Tests)

Kotlin 1.6+ 支持重复注解,可直接使用多个 @Tag

@Tag("slow") 
@Tag("logarithms")
@Test
fun `Repeatable Tag Log to base 2 of 8 should be equal to 3`() {
    Assertions.assertEquals(3.0, calculator.log(2, 8))
}

⚠️ 注意:必须确保 @Tag 在字节码层面是可重复的(即标注 @java.lang.annotation.Repeatable),否则 Java 调用方会出错。JUnit 5 已内置支持。

6. 总结

JUnit 5 的核心功能在 Kotlin 中均能良好运行,且得益于 Kotlin 的语法特性(如反引号命名、lambda 简写),部分场景下的测试代码反而比 Java 更清晰。

关键适配点:

  • 多断言需手动包装 Executable
  • @MethodSource 需用 @JvmStatic 暴露方法
  • 善用集合映射简化数据驱动测试

完整示例代码见:GitHub - Baeldung/kotlin-tutorials


原始标题:JUnit 5 for Kotlin Developers