SkipUnit 单元测试框架
Skip 的基础 Gradle 插件模块及单元测试支持,可将 Swift XCUnit 测试用例适配为转译后的 Kotlin JUnit 测试用例。
SkipUnit 提供 skip.unit Kotlin 包,其中包含从 Swift XCTest 接口到 Java/Kotlin JUnit 测试框架的适配。配合 Skip 转译器 ↗,它可以自动把 XCUnit 测试用例转译为 JUnit 测试,从而通过一致性测试发现并定位 Swift 代码与转译后 Skip Kotlin 代码之间的差异。
SkipUnit 依赖 skip ↗ 转译器插件,不包含其他库依赖。
它属于 Skip 核心框架,不应由应用直接导入。Skip 转译器会把 import XCUnit 转换为 import skip.unit.*,从而透明地引入此模块。
一致性测试是 Skip 开发的核心环节,用来确保 Swift 与 Kotlin 的行为完全相同。
执行一致性测试不需要导入任何 Skip 专用库或 API。标准的 XCTest、Foundation 模块以及项目自身的库依赖,足以完成大多数测试操作。
不过,测试必须同时支持 macOS 和 iOS,因为 Skip 测试只会在选择 macOS 运行目标时执行。详见从 Xcode 运行测试。
如果 Swift 代码不需要访问设备专用服务或其他 iOS 特有能力,最快的开发方式是在 macOS 上构建并运行。大多数常用 API 在 macOS 和 iOS 上行为一致;针对应用的 macOS 版本运行本地测试,可以缩短单元测试反馈周期,无论是在 Xcode 中交互开发,还是通过 swift test 接入持续集成(CI)。需要 iOS 特有功能的测试,可以放入 #if os(iOS) 条件块中,使其只在面向 iOS 模拟器或真机时运行。
同样,运行转译后的 Kotlin/Gradle 测试时,最快的方式是在本地直接运行 JUnit 测试,而不启动 Android 模拟器或连接设备。SkipFoundation 底层使用的 Java 和 Kotlin API 足以满足许多测试需求;需要 Android 专用 API 时,则由 Robolectric 框架提供兼容的 Android API 集合。
请牢记:Robolectric 并不是真正的 Android。本地 Robolectric 测试环境与真实 Android 模拟器或设备相似,但并不完全相同。Robolectric 缺少部分 Android API,而且本地执行的 Java 字节码也可能与真实 Android 环境中的 Dalvik/ART 字节码存在差异。如果这造成问题,请按照下文说明,改为在 Android 设备或模拟器上运行测试。
六种测试模式可归纳如下:
| Swift | Kotlin | 真实度 | 速度 | |
|---|---|---|---|---|
| 本地 | macOS + Cocoa API | macOS + Robolectric API | 最低 | 最高 |
| 模拟器 | iOS 模拟器 | Android 模拟器 | 较高 | 较慢 |
| 真机 | iOS 设备 | Android 设备 | 最高 | 最慢 |
默认情况下,Skip 使用 Robolectric 模拟的 Android 环境运行转译测试。若要改在 Android 设备或模拟器上运行,请通过 ANDROID_SERIAL 环境变量设置设备或模拟器 ID。可以在 Xcode scheme 的 Run 操作参数中为目标设置,也可以在命令行中设置标准环境变量:
> ANDROID_SERIAL=emulator-5554 swift testSkip Lite 支持使用 XCTest 以及 Swift Testing 的一个子集编写转译测试。
XCTest
Section titled “XCTest”下面是一个使用 XCTest 的标准 Skip 测试:
import XCTest
final class MyUnitTests: XCTestCase { func testSomeLogic() throws { XCTAssertEqual(1 + 2, 3, "basic test") }}Swift Testing
Section titled “Swift Testing”也可以使用 Swift Testing 的 @Test 和 @Suite 注解编写测试。#expect 宏会映射为断言函数,#require 则映射为 requireNotNil:
import Testing
@Suite struct MathTests { @Test func addition() { #expect(1 + 1 == 2) }
@Test func inequality() { #expect(3 != 5) }}还支持不位于结构体或类中的独立 @Test 函数;它们会自动包装进为 JUnit 生成的测试类:
import Testing
@Test func addition() { #expect(1 + 2 == 3)}目前支持以下 Swift Testing 功能:
@Test函数(成员方法和独立函数均可)@Suite类型#expect:支持相等(==)、不等(!=)、比较(>、<、>=、<=)和布尔表达式#require:用于可选值解包
目前尚不能转译 Swift Testing 的全部功能;暂不支持参数化测试、trait 和标签。
注意:Skip 转译器目前无法访问被测模块的内部 API。如果使用 Swift 的 @testable import 测试内部 API,转译器在转换测试代码时将无法执行常规的类型推断。这只意味着测试内部 API 时需要更明确地标注类型,并使用完全限定的值,例如使用 MyType.value,而不是简写为 .value。
转译后的单元测试设计为标准 Xcode 和 Swift Package Manager 测试流程的一部分。
测试由名为 XCSkipTests.swift 的测试入口文件驱动。如果测试目标中还没有这个文件,Skip 构建插件会在构建期间自动生成。因此,大多数项目无需自行创建或维护该文件,只需运行 swift test,插件便会生成测试入口。
如果需要自定义测试入口,例如指定设备目标或调整 Gradle 任务,可以在测试目标中加入自己的 XCSkipTests.swift;构建插件会优先使用它,不再自动生成:
#if os(macOS) // Skip 转译测试只在 macOS 目标上运行import SkipTest
/// 此测试用例将运行 Skip 模块的转译测试。@available(macOS 13, *)final class XCSkipTests: XCTestCase, XCGradleHarness { public func testSkipModule() async throws { try await runGradleTests(device: .none) // 设置设备 ID 后将在 Android 模拟器中运行,而不是使用 Robolectric }}#endif从 Xcode 运行测试
Section titled “从 Xcode 运行测试”只要选择 macOS 运行目标进行测试,转译后的测试用例就会自动执行。因此,需要确保 Swift 代码在 macOS 和 iOS 上都能以相同方式编译和运行。这是 Skip 一致性测试的前提:它会在 macOS 上运行 XCUnit 测试,同时在 Android 测试环境中运行转译后的 Kotlin 测试。虽然许多 Foundation 和 SwiftUI API 在 macOS 与 iOS 上完全相同,但偶尔仍需处理细微的平台差异。
转译后的单元测试会在 macOS 主机上启动 gradle test 进程,并针对 Skip 转译器插件的输出目录执行。随后解析 JUnit 测试输出的 XML 文件,并生成测试结果汇总报告。
从 CLI/CI 运行测试
Section titled “从 CLI/CI 运行测试”在 macOS 上运行 swift test 会自动执行测试转译。它既可用于本地无界面测试,也可用于持续集成(CI)服务器。
请注意,运行测试用例也会触发 Gradle 构建,并导致 Gradle 下载模块所需的全部库依赖。如果测试依赖 SkipUI 等框架,而该框架又依赖许多 Jetpack Compose 库,~/.gradle/ 目录中的依赖总量可能超过 1 GB。
因此首次测试可能较慢,看起来像是卡住或耗时异常。后续运行会复用已缓存的依赖,速度会明显提升。
从 Android Studio 运行测试
Section titled “从 Android Studio 运行测试”模块和测试完成转译后,可以直接从 Android Studio 运行 JUnit 测试。若要打开通过 skip init 创建的模块测试,请进入模块的 Skip/build/<module>.output/<module>Tests/skipstone 目录,按住 Control 点击 settings.gradle.kts 文件,然后从快捷菜单中选择“Open with External Editor”。
在 Android Studio 中运行测试可以跳过 iOS 测试,并单独运行和调试某个 Android 测试。这对排查 Android 专有问题很有帮助。
XCTest 与 JUnit 对测试失败的处理方式不同。
Swift 中出现 XCTAssert* 失败时,测试会记录失败但继续执行;Kotlin 中适配后的 assert* 失败会通过异常发出信号,并终止当前测试方法。因此,当一次测试中出现多个 XCTAssert* 失败时,两个环境报告的失败数量可能不同。XCTFail 同样如此,但 XCTSkip 除外;若要阻止测试在某个环境中运行,推荐且受支持的方式就是使用 XCTSkip。
Swift XCUnit 测试用例到 Kotlin JUnit 的适配非常直接。例如:
fun XCTAssertEqual(a: Any?, b: Any?): Unit = org.junit.Assert.assertEquals(b, a)转译后的 Kotlin 测试用例
Section titled “转译后的 Kotlin 测试用例”虽然通常不需要直接接触生成结果,但上方示例测试转译后的 Kotlin 代码如下:
package app.module.name
import skip.lib.*
import skip.unit.*
internal class MyUnitTests: XCTestCase { @Test internal fun testSomeLogic() = XCTAssertEqual(1 + 2, 3, "basic test")}skiptools/skip-unit ↗ 最近的 10 个发布版本:
- 版本 1.7.1 ↗ — 2026-08-19
- 版本 1.7.0 ↗ — 2026-05-25
- 版本 1.6.2 ↗ — 2026-05-23
- 版本 1.6.1 ↗ — 2026-04-07
- 版本 1.6.0 ↗ — 2026-03-12
- 版本 1.5.0 ↗ — 2026-02-04
- 版本 1.4.0 ↗ — 2025-09-13
- 版本 1.3.7 ↗ — 2025-08-13
- 版本 1.3.6 ↗ — 2025-08-01
- 版本 1.3.5 ↗ — 2025-08-01
完整发布历史:github.com/skiptools/skip-unit/releases ↗ — Atom 订阅 ↗