使用 XCTSkip 跳过测试
XCTSkip your tests
2020年6月25日
一句话判断
终于不用再在测试里写 return 或者 #if canImport 来跳过测试了——XCTSkip 是一个简单但极其实用的 API,用对了能让你的测试报告清晰得多。
这场 Session 讲了什么
Xcode 12 引入了 XCTSkip 系列 API,让开发者可以在测试运行时优雅地跳过特定测试用例。在此之前,跳过测试的方式五花八门:有人用 guard + return 静默跳过,有人用 #if 编译条件排除整个测试,还有人干脆把不需要的测试注释掉。这些做法的共同问题是——测试报告中看不到任何”跳过”的痕迹,你无法区分”这个测试没跑”和”这个测试根本不存在”。
Session 介绍了三种跳过方式:XCTSkip(无条件跳过)、XCTSkipIf(条件为真时跳过)和 XCTSkipUnless(条件为假时跳过)。每个跳过操作都会在测试报告中以黄色标记展示,附带开发者提供的跳过原因说明。这让测试报告变成了一个完整的状态面板——通过、失败、跳过各有多少,一目了然。
典型的使用场景包括:只在特定设备上运行的测试(如相机功能测试在模拟器上跳过)、依赖特定 OS 版本的 API 测试、需要特定硬件的测试(如 LiDAR 传感器)以及需要网络连接的集成测试。
值得深挖的点
XCTSkip 与测试计划的配合
Xcode 12 的测试计划(Test Plan)功能可以配置不同环境下运行不同的测试集。但 XCTSkip 提供了一种更灵活的方案——在代码层面根据运行时条件决定是否跳过。比如你的测试代码可以检测当前设备是否支持 ARKit,如果不支持就 XCTSkip,而不是在测试计划中手动排除。这种方式的优势是测试代码自带了运行条件说明,新团队成员一看就懂。
跳过 vs 失败的语义区别
Session 强调了一个重要的语义区分:跳过是”我知道这个测试在当前环境下不应该运行”,而失败是”这个测试应该通过但没有”。如果你的测试因为缺少前置条件(如服务器未启动)而失败,它应该被标记为跳过而非失败。这样 CI 的红灯只会因为真正的代码问题亮起,不会被环境问题干扰。
代码片段
import XCTest
class CameraTests: XCTestCase {
// 条件跳过:模拟器上没有相机
func testCameraCapture() throws {
// 在模拟器上跳过,因为没有真实相机
try XCTSkipIf(
TargetEnvironment.isSimulator,
"相机测试需要真机运行"
)
// 只在真机上执行的相机测试代码
let camera = CameraController()
let photo = try camera.capture()
XCTAssertNotNil(photo)
}
// 条件跳过:检查 OS 版本
func testNewPhotoAPI() throws {
try XCTSkipIf(
#available(iOS 14, *) == false,
"此测试需要 iOS 14 或更高版本"
)
// iOS 14+ 的新 API 测试
// ...
}
}
// 使用 XCTSkipUnless 确保前置条件满足
import XCTest
class NetworkTests: XCTestCase {
func testAPIEndpoint() throws {
let url = URL(string: "https://api.example.com/health")!
// 检查服务器是否可达,不可达则跳过
var response: URLResponse?
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: url) { _, resp, _ in
response = resp
semaphore.signal()
}.resume()
semaphore.wait()
try XCTSkipUnless(
response != nil,
"测试服务器不可达,跳过网络测试"
)
// 确认服务器可达后执行实际测试
let expectation = self.expectation(description: "API 返回数据")
APIClient().fetchItems { result in
XCTAssertNotNil(try? result.get())
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
}
// 无条件跳过:标记未完成的功能测试
import XCTest
class FeatureTests: XCTestCase {
func testUpcomingFeature() throws {
// 功能尚未实现,暂时跳过
throw XCTSkip("功能 X 尚在开发中,暂时跳过此测试")
// 下面的代码不会执行,但在测试报告中会显示为"跳过"
// 而不是消失在注释中
}
// 检测特定硬件能力
func testLiDARScanning() throws {
try XCTSkipIf(
!ProcessInfo().isLiDARAvailable,
"LiDAR 扫描测试需要配备 LiDAR 的设备"
)
// LiDAR 相关测试...
}
}
// 辅助扩展
extension ProcessInfo {
var isLiDARAvailable: Bool {
// 检查设备是否支持 LiDAR
guard let device = AVCaptureDevice.default(
.builtInLiDARDepthCamera, for: .video, position: .back
) else { return false }
return true
}
}
最佳实践
- 总是为
XCTSkip提供清晰的原因描述,让团队成员知道为什么这个测试被跳过 - 在 CI 中配置测试报告统计跳过数量,如果跳过数突然增加,说明可能有环境问题或功能回退
- 不要用
XCTSkip替代修复失败的测试——如果测试本身有 bug,应该修复它而不是跳过 - 对需要外部依赖的测试(网络、蓝牙、特定硬件),在测试方法开头统一做前置条件检查
- 把条件跳过逻辑封装成辅助方法(如
requirePhysicalDevice()),提高可读性和复用性
还有什么值得关注
- Xcode 的测试报告中,跳过的测试会以黄色图标显示,与绿色(通过)和红色(失败)明确区分
XCTSkip可以在setUp()方法中使用,一次性跳过整个测试类的所有测试XCTSkip与 Swift 的throw机制完美集成——try+throw XCTSkip()的写法非常自然