3. 断言的艺术:XCTAssert家族详解
断言这东西,说白了就是测试里的「裁判」。
我刚开始写单元测试时,总觉得断言就是随便写个 XCTAssertTrue 就完事了。后来被坑过几次才明白——选对断言,比写对逻辑还重要。你想想看,一个模糊的断言,可能让 bug 悄悄溜过去,而一个精准的断言,能直接把问题揪出来。
3.1 布尔断言:最基础也最容易出错
布尔断言是 XCTest 里最常用的,也是最容易被滥用的。
// 基础用法
XCTAssertTrue(success, "操作应该成功");
XCTAssertFalse(failure, "操作应该失败");
嗯,这里要注意:别把 XCTAssertTrue 当成万能断言。我在项目中遇到过这样的代码:
// ❌ 糟糕的写法
XCTAssertTrue(result == expected, "结果应该相等");
// ✅ 正确的写法
XCTAssertEqual(result, expected, "结果应该相等");
为什么说前者不好?因为当断言失败时,你只能看到「条件为假」,却不知道 result 和 expected 具体差了多少。而 XCTAssertEqual 会直接告诉你两个值的差异。
核心原则:能用具体断言,就别用布尔断言。布尔断言只适合判断「是/否」的场景。
3.2 相等性断言:你的第一道防线
相等性断言是测试里最常用的断言类型。我个人习惯把它们分成三类:
| 断言方法 | 适用场景 | 注意事项 |
|---|---|---|
| XCTAssertEqual | 值类型相等判断 | 使用 == 比较 |
| XCTAssertEqualObjects | 对象相等判断 | 使用 isEqual: 方法 |
| XCTAssertEqualWithAccuracy | 浮点数比较 | 需要指定精度 |
浮点数比较是个大坑。我记得有一次测试一个计算器功能,明明逻辑没问题,测试就是过不了。查了半天才发现是浮点数精度问题。
// ❌ 浮点数直接比较,大概率会失败
XCTAssertEqual(0.1 + 0.2, 0.3, "浮点数加法");
// ✅ 带精度的比较
XCTAssertEqualWithAccuracy(0.1 + 0.2, 0.3, accuracy: 0.0001, "浮点数加法");
避坑指南:我曾经因为浮点数比较没加精度,导致 CI 构建时断时续地失败。后来才发现,不同架构的 CPU 对浮点数的处理有细微差异。所以,所有浮点数比较都必须指定精度。
3.3 可选值断言:Swift 时代的必备技能
Swift 的可选值(Optional)让代码更安全,但也给测试带来了新挑战。XCTest 提供了专门的断言来处理可选值:
// 断言可选值不为 nil
let result = fetchData()
XCTAssertNotNil(result, "数据不应该为空")
// 断言可选值为 nil
let cachedData = getCache()
XCTAssertNil(cachedData, "缓存应该为空")
// 解包并比较
XCTAssertEqual(result?.count, 10, "数据长度应该为10")
这里有个小技巧:尽量用 XCTAssertNotNil 而不是强制解包。我见过有人这样写:
// ❌ 危险写法
let value = optionalValue!
XCTAssertEqual(value, expected)
// ✅ 安全写法
XCTAssertNotNil(optionalValue)
XCTAssertEqual(optionalValue, expected)
为什么?因为如果 optionalValue 是 nil,第一种写法会直接 crash,而第二种写法会优雅地报告测试失败。
个人经验:我习惯在测试方法开头先做 nil 检查,然后再做具体断言。这样即使数据有问题,测试也能给出清晰的失败信息,而不是莫名其妙的崩溃。
3.4 异常断言:测试你的错误处理
好的代码不仅要处理正常情况,还要优雅地处理异常。XCTest 提供了两种方式来测试异常:
// 方式一:断言会抛出异常
XCTAssertThrowsError(try riskyOperation(), "应该抛出错误")
// 方式二:断言不会抛出异常
XCTAssertNoThrow(try safeOperation(), "不应该抛出错误")
// 方式三:捕获并验证异常内容
XCTAssertThrowsError(try riskyOperation()) { error in
XCTAssertEqual(error as? MyError, MyError.invalidInput)
}
你想想看,如果不测试异常情况,你的代码就像没有安全气囊的车——正常行驶没问题,但一旦出事就完了。
我记得有一次重构网络层,改了一个错误处理的逻辑。自测时所有正常流程都通过了,但上线后用户反馈说「网络错误时 App 会闪退」。后来加上了异常断言测试,才发现是新的错误处理代码在某些边界条件下没覆盖到。
最佳实践:每个可能抛出异常的方法,都应该至少写两个测试——一个测试正常流程,一个测试异常流程。异常测试里要验证:
- 异常是否被正确抛出
- 异常类型是否正确
- 异常信息是否清晰
3.5 断言的艺术:选对工具,事半功倍
最后,我想分享一个我总结的「断言选择矩阵」:
| 场景 | 推荐断言 | 不推荐 |
|---|---|---|
| 判断布尔值 | XCTAssertTrue/False | XCTAssertEqual(true, ...) |
| 比较数值 | XCTAssertEqual | XCTAssertTrue(a == b) |
| 比较浮点数 | XCTAssertEqualWithAccuracy | XCTAssertEqual |
| 检查 nil | XCTAssertNotNil/Nil | 强制解包 + 比较 |
| 测试异常 | XCTAssertThrowsError | do-catch + XCTFail |
说白了,断言的选择就是「精确性」和「可读性」的平衡。选对了,测试代码就像文档一样清晰;选错了,测试代码反而成了新的负担。
嗯,这一章就到这里。下一章我们会聊聊「测试替身」——那些帮你隔离依赖、让测试更纯粹的好工具。