# 教程 - 使用 Kotlin 和 JUnit 测试 Java 代码 Kotlin 与 Java 完全兼容互通, 因此你可以使用 Kotlin 为 Java 代码编写测试, 并与项目中已有的 Java 测试一起运行. 在本教程中, 你将学习如何: * 配置 Java-Kotlin 混合项目, 使用 [JUnit](https://junit.org/) 运行测试. * 添加 Kotlin 测试, 用于验证 Java 代码. * 使用 Maven 或 Gradle 运行测试. > **Note:** > 在开始之前, 请确认你已安装: > > > > * [IntelliJ IDEA](https://www.jetbrains.com/idea/download/), 或安装了 [Kotlin 扩展](https://github.com/Kotlin/kotlin-lsp/tree/main?tab=readme-ov-file#vs-code-quick-start) 的 [VS Code](https://code.visualstudio.com/Download). > > * Java 17 或更高版本. ## 配置项目 1. 在 IDE 中, 从版本控制系统中克隆示例项目: ```TEXT https://github.com/kotlin-hands-on/kotlin-junit-sample.git ``` 2. 进入 `initial` 模块, 查看项目结构: ```TEXT kotlin-junit-sample/ ├── initial/ │ ├── src/ │ │ ├── main/java/ # Java 源代码 │ │ └── test/java/ # Java 编写的 JUnit 测试 │ ├── pom.xml # Maven 配置 │ └── build.gradle.kts # Gradle 配置 ``` `initial` 模块包含一个简单的 Java Todo 应用程序, 带有一个测试. 3. 在同一目录中, 打开构建文件, 更新其内容, 以便支持 Kotlin: Maven: ```XML 4.0.0 org.jetbrains.kotlin kotlin-junit-complete 1.0-SNAPSHOT kotlin-junit-complete https://kotlinlang.org/docs/jvm-test-using-junit.htm UTF-8 17 1.6.0 2.4.20 org.junit junit-bom 6.0.3 pom import org.junit.jupiter junit-jupiter-api test org.junit.jupiter junit-jupiter-engine test org.junit.jupiter junit-jupiter-params test com.gitlab.klamonte jexer ${jexer.version} maven-clean-plugin 3.4.0 maven-resources-plugin 3.3.1 maven-surefire-plugin 3.3.0 maven-jar-plugin 3.4.2 maven-install-plugin 3.1.2 maven-deploy-plugin 3.1.2 maven-site-plugin 3.12.1 maven-project-info-reports-plugin 3.6.1 org.jetbrains.kotlin kotlin-maven-plugin ${kotlin.version} true ``` * 在 `` 部分, 设置 Kotlin 版本. * 在 `` 部分, 添加 JUnit Jupiter 依赖项, 以便运行测试. * 在 `` 部分, 应用 `kotlin-maven-plugin`, 并将 `` 设置为 `true`. 这个设置会自动向构建中添加相应的执行配置和 `kotlin-stdlib` 依赖项. * 使用带有 extensions 的 Kotlin Maven plugin 时, 不需要向 `` 部分添加 `maven-compiler-plugin`. Gradle: ```KOTLIN // build.gradle.kts group = "org.jetbrains.kotlin" version = "1.0-SNAPSHOT" description = "kotlin-junit-complete" java.sourceCompatibility = JavaVersion.VERSION_17 plugins { application kotlin("jvm") version "2.4.20" } kotlin { jvmToolchain(17) } application { mainClass.set("org.jetbrains.kotlin.junit.App") } repositories { mavenCentral() } dependencies { implementation("com.gitlab.klamonte:jexer:1.6.0") testImplementation(kotlin("test")) testImplementation(libs.org.junit.jupiter.junit.jupiter.api) testImplementation(libs.org.junit.jupiter.junit.jupiter.params) testRuntimeOnly(libs.org.junit.jupiter.junit.jupiter.engine) testRuntimeOnly(libs.org.junit.platform.junit.platform.launcher) } tasks.test { useJUnitPlatform() } ``` * 在 `plugins {}` 代码块中, 添加 `kotlin("jvm")` plugin. * 设置 JVM 工具链版本, 与 Java 版本匹配. * 在 `dependencies {}` 代码块中, 添加 `kotlin.test` 库, 该库提供 Kotlin 的测试工具, 与 JUnit 集成. Kotlin/JVM 支持 JUnit 的最新稳定版本, JUnit 6. 你可以在 `gradle/libs.versions.toml` 版本目录中找到它. 如果你更倾向于使用版本目录, 甚至可以在版本目录中添加 `kotlin("jvm")` plugin: ```TOML # gradle/libs.versions.toml [versions] kotlin = "2.4.20" junit = "6.0.3" [libraries] org-junit-jupiter-junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } org-junit-jupiter-junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } org-junit-jupiter-junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } org-junit-platform-junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } [plugins] kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } ``` 4. 在 IDE 中重新加载构建文件. 关于构建文件设置, 详情请参见 [项目配置](mixing-java-kotlin-intellij.html#project-configuration). ## 添加你的第一个 Kotlin 测试 `initial/src/test/java` 中的 `TodoItemTest.java` 测试已经验证了应用程序的基础功能: 包括条目创建, 默认值, 唯一 ID, 以及状态变更. 你可以通过添加 Kotlin 测试, 验证存储库级别的行为, 来扩展测试覆盖范围: 1. 进入相同的测试源代码目录 `initial/src/test/java`. 2. 在与 Java 测试相同的包中创建 `TodoRepositoryTest.kt` 文件. 3. 创建测试类, 包含字段声明和 setup 函数: ```KOTLIN package org.jetbrains.kotlin.junit import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.DisplayName internal class TodoRepositoryTest { lateinit var repository: TodoRepository lateinit var testItem1: TodoItem lateinit var testItem2: TodoItem @BeforeEach fun setUp() { repository = TodoRepository() testItem1 = TodoItem("Task 1", "Description 1") testItem2 = TodoItem("Task 2", "Description 2") } } ``` * JUnit 注解在 Kotlin 中的使用方式, 与在 Java 中相同. * 在 Kotlin 中, [lateinit 关键字](properties.html#late-initialized-properties-and-variables) 允许声明非 null 属性, 并在之后进行初始化. 这有助于避免在测试中使用可为 null 的类型(`TodoRepository?`). 4. 在 `TodoRepositoryTest` 类中添加一个测试, 检查存储库的初始状态及其大小: ```KOTLIN @Test @DisplayName("Should start with empty repository") fun shouldStartEmpty() { Assertions.assertEquals(0, repository.size()) Assertions.assertTrue(repository.all.isEmpty()) } ``` * 与 Java 的静态导入不同, Jupiter 的 `Assertions` 是作为类导入的, 并用作断言函数的限定符. * 你可以在 Kotlin 中以 `repository.all` 的方式将 Java 的 getter 作为属性访问, 而不必调用 `.getAll()`. 5. 再编写一个测试, 验证所有条目的复制行为: ```KOTLIN @Test @DisplayName("Should return defensive copy of items") fun shouldReturnDefensiveCopy() { repository.add(testItem1) val items1 = repository.all val items2 = repository.all Assertions.assertNotSame(items1, items2) Assertions.assertThrows( UnsupportedOperationException::class.java ) { items1.clear() } Assertions.assertEquals(1, repository.size()) } ``` * 要从 Kotlin 类获取 Java 类对象, 请使用 `::class.java`. * 你可以将复杂的断言拆分为多行, 不需要使用任何特殊的续行字符. 6. 添加一个测试, 验证通过 ID 查找条目的功能: ```KOTLIN @Test @DisplayName("Should find item by ID") fun shouldFindItemById() { repository.add(testItem1) repository.add(testItem2) val found = repository.getById(testItem1.id()) Assertions.assertTrue(found.isPresent) Assertions.assertEquals(testItem1, found.get()) } ``` Kotlin 能够与 Java 的 [Optional API](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) 无缝协作. 它会自动将 getter 方法转换为属性, 所以 `isPresent()` 方法在这里可以作为属性访问. 7. 编写一个测试, 验证条目的删除机制: ```KOTLIN @Test @DisplayName("Should remove item by ID") fun shouldRemoveItemById() { repository.add(testItem1) repository.add(testItem2) val removed = repository.remove(testItem1.id()) Assertions.assertTrue(removed) Assertions.assertEquals(1, repository.size()) Assertions.assertTrue(repository.getById(testItem1.id()).isEmpty) Assertions.assertTrue(repository.getById(testItem2.id()).isPresent) } @Test @DisplayName("Should return false when removing non-existent item") fun shouldReturnFalseForNonExistentRemoval() { repository.add(testItem1) val removed = repository.remove("non-existent-id") Assertions.assertFalse(removed) Assertions.assertEquals(1, repository.size()) } ``` 在 Kotlin 中, 可以链式调用方法和属性访问, 例如 `repository.getById(id).isEmpty`. > **Tip:** > 你可以向 `TodoRepositoryTest` 测试类添加更多测试, 覆盖额外的功能. 请在示例项目的 [complete](https://github.com/kotlin-hands-on/kotlin-junit-sample/blob/main/complete/src/test/java/org/jetbrains/kotlin/junit/TodoRepositoryTest.kt) 模块中, 查看完整的代码. ## 运行测试 运行 Java 和 Kotlin 测试, 验证项目是否正确工作: 1. 使用侧栏图标(gutter icon)运行测试: ![运行测试](images/run-test.png) 也可以从 `initial` 目录, 使用命令行运行所有的项目测试: Maven: ```BASH mvn test ``` Gradle: ```BASH ./gradlew test ``` 2. 修改某个变量值, 检查测试是否正确工作. 例如, 修改 `shouldAddItem` 测试, 期望出现一个错误的存储库大小: ```KOTLIN @Test @DisplayName("Should add item to repository") fun shouldAddItem() { repository.add(testItem1) Assertions.assertEquals(2, repository.size()) // 从 1 改为 2 Assertions.assertTrue(repository.all.contains(testItem1)) } ``` 3. 再次运行测试, 确认测试失败: ![检查测试结果. 测试失败](images/test-failed.png) > **Tip:** > 你可以在示例项目的 [complete](https://github.com/kotlin-hands-on/kotlin-junit-sample/tree/main/complete) 模块中, 找到完整配置的项目, 以及测试代码. ## 下一步做什么? 了解更多关于 [使用 Maven 测试 Kotlin 项目](jvm-test-maven.html) 的内容.