Kotlin Multiplatform Development Help

测试你的跨平台应用 − 教程

在本教程中,你将学习如何在 Kotlin 跨平台应用中创建、配置和运行测试。

跨平台项目的测试可分为两类:

  • 通用代码测试。这些测试可使用任何支持的框架在任何平台上运行。

  • 平台特定代码测试。这些测试对验证平台特定逻辑至关重要,它们使用平台特定框架,并能利用其附加功能(如更丰富的 API 和更广泛的断言)。

跨平台项目同时支持这两类测试。本教程将首先展示如何在简单的 Kotlin 跨平台项目中为通用代码设置、创建和运行单元测试。随后,你将通过一个更复杂的示例,学习如何为通用代码和平台特定代码编写测试。

测试简单跨平台项目

创建项目

  1. 准备跨平台开发环境。 检查必要工具列表并按需更新至最新版本

  2. 打开 Kotlin 跨平台向导

  3. New project 选项卡中,确保选中 AndroidiOS 选项。

  4. 对于 iOS,选择 Do not share UI 选项(本教程无需此功能)。

  5. 点击 Download 按钮并解压生成的压缩包。

Kotlin 跨平台向导

编写代码

  1. 启动 Android Studio。

  2. 在欢迎界面点击 Open ,或通过编辑器菜单选择 File | Open

  3. 导航至解压的项目文件夹,点击 Open

    Android Studio 会检测到该文件夹包含 Gradle 构建文件,并将其作为新项目打开。

  4. Android Studio 默认视图针对 Android 开发优化。为获得更适合跨平台开发的完整项目文件结构,请将视图从 Android 切换至 Project

    选择项目视图
  5. shared/src/commonMain/kotlin 目录下创建 common.example.search 子目录。

  6. 在此目录中创建 Kotlin 文件 Grep.kt ,并添加以下函数:

    fun grep(lines: List<String>, pattern: String, action: (String) -> Unit) { val regex = pattern.toRegex() lines.filter(regex::containsMatchIn) .forEach(action) }

    该函数模拟 UNIX grep 命令的功能,接收文本行列表、作为正则表达式的模式,以及当行匹配模式时触发的函数。

添加测试

现在测试通用代码。关键部分是为通用测试创建源集,并添加 kotlin.test API 库作为依赖。

  1. shared 目录中打开 build.gradle.kts 文件。添加通用测试源集及 kotlin.test 依赖:

    sourceSets { //... commonTest.dependencies { implementation(libs.kotlin.test) } }
  2. 添加依赖后,系统会提示同步项目。点击 Sync Now 同步 Gradle 文件:

    同步 Gradle 文件
  3. commonTest 源集存储所有通用测试。现在需要在项目中创建同名目录:

    1. 右键点击 shared/src 目录,选择 New | Directory 。IDE 将显示选项列表。

    2. 输入 commonTest/kotlin 路径筛选选项,然后从列表中选择:

      创建通用测试目录
  4. commonTest/kotlin 目录中创建 common.example.search 包。

  5. 在此包中创建 Grep.kt 文件并添加以下单元测试:

    import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals class GrepTest { companion object { val sampleData = listOf( "123 abc", "abc 123", "123 ABC", "ABC 123" ) } @Test fun shouldFindMatches() { val results = mutableListOf<String>() grep(sampleData, "[a-z]+") { results.add(it) } assertEquals(2, results.size) for (result in results) { assertContains(result, "abc") } } }

可以看到,导入的注解和断言既不依赖平台也不依赖框架。运行测试时,平台特定框架将提供测试运行器。

探索 kotlin.test API

kotlin.test 库提供与平台无关的注解和断言。 Test 等注解会映射到所选框架提供的注解或其最接近的等效项。

断言通过 Asserter 接口的实现执行。该接口定义了测试中常用的各种检查操作。API 提供默认实现,但通常你会使用框架特定的实现。

例如,JVM 支持 JUnit 4、JUnit 5 和 TestNG 框架。在 Android 上, assertEquals() 调用可能由 JUnit4Asserter 实例执行。在 iOS 上, Asserter 类型的默认实现与 Kotlin/Native 测试运行器配合使用。

运行测试

可通过以下方式执行测试:

  • 使用行号旁的 Run 图标运行 shouldFindMatches() 测试函数。

  • 通过右键菜单运行整个测试文件。

  • 使用类旁的 Run 图标运行 GrepTest 测试类。

还可使用快捷键 ⌃ ⇧ F10/Ctrl+Shift+F10 。无论选择哪种方式,都会显示可运行测试的目标平台列表:

运行测试任务

对于 android 选项,测试使用 JUnit 4 运行。对于 iosSimulatorArm64 ,Kotlin 编译器检测测试注解并创建由 Kotlin/Native 测试运行器执行的 测试二进制文件

以下是测试成功运行的输出示例:

测试输出

处理更复杂的项目

为通用代码编写测试

你已为 grep() 函数创建了通用代码测试。现在来看更高级的通用代码测试示例 ——CurrentRuntime 类。该类包含代码运行平台的信息,例如 Android 单元测试在本地 JVM 运行时可能显示 "OpenJDK" 和 "17.0"。

CurrentRuntime 实例应通过平台名称(字符串)和可选版本号创建。当存在版本号时,只需提取字符串开头的数字部分(如果存在)。

  1. commonMain/kotlin 目录中创建 org.kmp.testing 子目录。

  2. 在此目录中创建 CurrentRuntime.kt 文件并添加以下实现:

    class CurrentRuntime(val name: String, rawVersion: String?) { companion object { val versionRegex = Regex("^[0-9]+(\\.[0-9]+)?") } val version = parseVersion(rawVersion) override fun toString() = "$name version $version" private fun parseVersion(rawVersion: String?): String { val result = rawVersion?.let { versionRegex.find(it) } return result?.value ?: "unknown" } }
  3. commonTest/kotlin 目录中创建 org.kmp.testing 包。

  4. 在此包中创建 CurrentRuntimeTest.kt 文件并添加以下平台和框架无关的测试:

    import kotlin.test.Test import kotlin.test.assertEquals class CurrentRuntimeTest { @Test fun shouldDisplayDetails() { val runtime = CurrentRuntime("MyRuntime", "1.1") assertEquals("MyRuntime version 1.1", runtime.toString()) } @Test fun shouldHandleNullVersion() { val runtime = CurrentRuntime("MyRuntime", null) assertEquals("MyRuntime version unknown", runtime.toString()) } @Test fun shouldParseNumberFromVersionString() { val runtime = CurrentRuntime("MyRuntime", "1.2 Alpha Experimental") assertEquals("MyRuntime version 1.2", runtime.toString()) } @Test fun shouldHandleMissingVersion() { val runtime = CurrentRuntime("MyRuntime", "Alpha Experimental") assertEquals("MyRuntime version unknown", runtime.toString()) } }

可通过 IDE 提供的任意方式 运行此测试。

添加平台特定测试

现在你已掌握通用代码测试的编写,接下来为 Android 和 iOS 编写平台特定测试。

为创建 CurrentRuntime 实例,在通用的 CurrentRuntime.kt 文件中声明以下函数:

expect fun determineCurrentRuntime(): CurrentRuntime

该函数需在每个支持的平台上分别实现,否则构建将失败。除了在各平台实现此函数外,还需提供测试。下面为 Android 和 iOS 创建测试。

Android 测试

  1. androidMain/kotlin 目录中创建 org.kmp.testing 包。

  2. 在此包中创建 AndroidRuntime.kt 文件,并实现预期的 determineCurrentRuntime() 函数:

    actual fun determineCurrentRuntime(): CurrentRuntime { val name = System.getProperty("java.vm.name") ?: "Android" val version = System.getProperty("java.version") return CurrentRuntime(name, version) }
  3. shared/src 目录中创建测试目录:

    1. 右键点击 shared/src 目录,选择 New | Directory

    2. 输入 androidUnitTest/kotlin 路径筛选选项,然后从列表中选择:

    创建 Android 测试目录
  4. kotlin 目录中创建 org.kmp.testing 包。

  5. 在此包中创建 AndroidRuntimeTest.kt 文件并添加以下 Android 测试:

    import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals class AndroidRuntimeTest { @Test fun shouldDetectAndroid() { val runtime = determineCurrentRuntime() assertContains(runtime.name, "OpenJDK") assertEquals(runtime.version, "17.0") } }

Android 特定测试在本地 JVM 上运行可能看似奇怪。这是因为这些测试作为本地单元测试在当前机器上运行。如 Android Studio 文档 所述,这些测试与在设备或模拟器上运行的插桩测试不同。

你可以在项目中添加其他类型的测试。关于插桩测试,请参阅 Touchlab 指南

iOS 测试

  1. iosMain/kotlin 目录中创建 org.kmp.testing 子目录。

  2. 在此目录中创建 IOSRuntime.kt 文件,并实现预期的 determineCurrentRuntime() 函数:

    import kotlin.experimental.ExperimentalNativeApi import kotlin.native.Platform @OptIn(ExperimentalNativeApi::class) actual fun determineCurrentRuntime(): CurrentRuntime { val name = Platform.osFamily.name.lowercase() return CurrentRuntime(name, null) }
  3. shared/src 目录中创建新目录:

    1. 右键点击 shared/src 目录,选择 New | Directory

    2. 输入 iosTest/kotlin 路径筛选选项,然后从列表中选择:

    创建 iOS 测试目录
  4. iosTest/kotlin 目录中创建 org.kmp.testing 子目录。

  5. 在此目录中创建 IOSRuntimeTest.kt 文件并添加以下 iOS 测试:

    package org.kmp.testing import kotlin.test.Test import kotlin.test.assertEquals class IOSRuntimeTest { @Test fun shouldDetectOS() { val runtime = determineCurrentRuntime() assertEquals(runtime.name, "ios") assertEquals(runtime.version, "unknown") } }

运行多个测试并分析报告

此时,你已拥有通用、Android 和 iOS 的实现代码及其测试。项目目录结构应类似如下:

完整项目结构

可通过右键菜单或快捷键运行单个测试。另一种方式是使用 Gradle 任务。例如,运行 allTests Gradle 任务会使用相应的测试运行器执行项目中所有测试:

Gradle 测试任务

运行测试时,除了 IDE 中的输出外,还会生成 HTML 报告。你可以在 shared/build/reports/tests 目录中找到它们:

跨平台测试的 HTML 报告

运行 allTests 任务并检查生成的报告:

  • allTests/index.html 文件包含通用测试和 iOS 测试的合并报告(iOS 测试依赖通用测试,在其之后运行)。

  • testDebugUnitTesttestReleaseUnitTest 文件夹包含两种默认 Android 构建变体的报告(目前 Android 测试报告不会自动合并到 allTests 报告中)。

跨平台测试的 HTML 报告

跨平台项目测试使用规则

你已学会在 Kotlin 跨平台应用中创建、配置和执行测试。在未来的项目中,请记住:

  • 编写通用代码测试时,仅使用 kotlin.test 等多平台库。将依赖添加到 commonTest 源集。

  • kotlin.test API 中的 Asserter 类型应仅间接使用。虽然 Asserter 实例可见,但测试中无需直接调用。

  • 始终遵循测试库 API。编译器与 IDE 会阻止你使用框架特定功能。

  • 虽然 commonTest 中使用的测试框架不影响结果,但建议用每个计划使用的框架运行测试,以验证开发环境配置正确。

  • 编写平台特定代码测试时,可使用对应框架的功能(如注解和扩展)。

  • 可通过 IDE 或 Gradle 任务运行测试。

  • 运行测试时会自动生成 HTML 测试报告。

下一步?

23 四月 2025