Kotlin Multiplatform Development Help

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

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

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

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

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

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

测试简单跨平台项目

创建项目

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

  2. 打开 Kotlin 跨平台向导。

  3. 在 New project 选项卡中,确保选中 Android 和 iOS 选项。

  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 测试依赖通用测试,在其之后运行)。

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

跨平台测试的 HTML 报告

跨平台项目测试使用规则

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

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

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

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

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

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

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

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

下一步?

23 四月 2025