Kotlin 测试

Kotlin 可以测试 Kotlin 代码,也可以测试 Java 代码。由于 Kotlin 与 Java 高度互操作,JUnit、Gradle、Maven、Mockito、AssertJ 等 Java 测试生态大多可以继续使用。同时,Kotlin 标准测试库 kotlin.test 提供了跨平台测试抽象。

JVM 测试基本配置

Gradle Kotlin DSL:

plugins {
    kotlin("jvm") version "2.4.0"
}

kotlin {
    jvmToolchain(17)
}

dependencies {
    testImplementation(kotlin("test"))
}

tasks.test {
    useJUnitPlatform()
}

kotlin("test") 提供 Kotlin 测试工具,并能与 JUnit 集成。JVM 项目通常使用 JUnit Platform。

使用 JUnit

import kotlin.test.Test
import kotlin.test.assertEquals

class CalculatorTest {
    @Test
    fun addsNumbers() {
        assertEquals(4, 2 + 2)
    }
}

也可以直接使用 JUnit Jupiter:

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class CalculatorTest {
    @Test
    fun addsNumbers() {
        assertEquals(4, 2 + 2)
    }
}

kotlin.test 的好处是更容易迁移到多平台测试;JUnit Jupiter 的好处是 JVM 生态能力完整。

用 Kotlin 测 Java 代码

Java:

public class TodoRepository {
    public List<TodoItem> getAll() {
        return List.of();
    }
}

Kotlin 测试:

class TodoRepositoryTest {
    private lateinit var repository: TodoRepository

    @BeforeEach
    fun setUp() {
        repository = TodoRepository()
    }

    @Test
    fun startsEmpty() {
        assertTrue(repository.all.isEmpty())
    }
}

注意 Kotlin 调 Java getter 时可以用属性语法:repository.all 实际调用 getAll()

lateinit 在测试中的使用

测试中常见:

private lateinit var service: UserService

@BeforeEach
fun setUp() {
    service = UserService()
}

这样可以避免把测试 fixture 声明为可空类型:

private var service: UserService? = null

lateinit 访问前必须初始化,否则会抛 UninitializedPropertyAccessException

断言异常

Kotlin test:

assertFailsWith<IllegalArgumentException> {
    User("")
}

JUnit Jupiter:

Assertions.assertThrows(IllegalArgumentException::class.java) {
    User("")
}

在 Java API 需要 Class<T> 时,用 ::class.java

测试命名

Kotlin 允许用反引号写可读测试名:

@Test
fun `user name must not be blank`() {
    assertFailsWith<IllegalArgumentException> {
        User("")
    }
}

这种写法适合测试,但不建议滥用在生产 API 中。部分 Java 工具、老旧测试报告或命令行过滤可能对空格方法名支持不佳。

多平台测试

KMP 项目中通常有:

src/commonTest/kotlin
src/jvmTest/kotlin
src/jsTest/kotlin
src/iosTest/kotlin

公共业务逻辑放 commonTest,平台相关行为放具体平台测试。

kotlin {
    sourceSets {
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

不要只跑 JVM 测试就认为多平台逻辑已验证。JS、Native、iOS 的运行时差异可能影响行为。

Mock 与 final 类

Kotlin 类默认 final。某些 Java Mock 框架默认依赖继承或代理,可能需要额外配置。

选择:

  • 使用支持 Kotlin final/mock 的框架配置。
  • 对接口编程。
  • 把复杂外部依赖抽象成接口。
  • 避免为了测试随意把所有类改成 open

Java Optional 与 Kotlin 可空测试

测试 Java API 时可能遇到 Optional

val found = repository.getById(id)
assertTrue(found.isPresent)
assertEquals(item, found.get())

Kotlin 自己的 API 通常更倾向返回可空类型:

val found: TodoItem? = repository.findById(id)
assertNotNull(found)

不要在 Kotlin API 中机械使用 Java Optional

实践建议

  • JVM 项目优先用 JUnit Platform。
  • 多平台库优先使用 kotlin.test
  • 测试 fixture 可以用 lateinit,但只限清晰初始化流程。
  • Kotlin 测 Java 代码时注意平台类型和 Java getter 属性映射。
  • 不要为了 Mock 破坏生产代码设计,优先抽接口和依赖注入。
  • CI 中明确运行所有目标的测试任务。

参考

  • 官方 Kotlin + JUnit 教程:https://kotlinlang.org/docs/jvm-test-using-junit.html