iOS自动化测试没那么可怕,XCTest真香
去年双11前夜,我们组的iOS App在提审前两天突然崩了——不是崩溃,是功能逻辑全乱套。产品经理急得在群里@所有人,测试同学更是连发三条“求救信号”。我作为研二狗,已经在实验室干了快两年,主攻分布式系统,但因为组里人手紧张,被迫兼职搞iOS自动化测试。说实话,当时看到那堆手动回归测试用例,真的想砸键盘。
但转念一想:这不就是个机会吗?反正迟早要面对CI/CD和质量保障,不如趁机把XCTest框架吃透。于是,我熬了两个通宵,硬是把核心流程的自动化测试跑通了。今天这篇,就来聊聊我在实战中踩过的坑、攒下的经验,以及为什么XCTest其实比你想象中好用得多。
为什么是XCTest?
先说背景:我们组做的其实是个区块链钱包类App(别问,问就是“资源”项目,懂的都懂)。这类应用对安全性、一致性要求极高,任何一笔交易出错都可能引发资损。而手动测试根本扛不住高频迭代——产品三天两头改UI,后端接口又经常灰度上线,测试同学天天喊“测不完”。
XCTest是Apple官方提供的测试框架,集成在Xcode里,支持单元测试(Unit Test)和UI测试(UI Test)。优势很明显:
- 原生支持Swift/SwiftUI,语法友好,和项目代码无缝衔接
- 无需额外依赖,不像某些第三方框架还要折腾Podfile
- 和Xcode深度集成,跑测试、看覆盖率、调试断点一气呵成
- App Store审核友好——毕竟用的是Apple自家工具,不会被当成“黑科技”拒审
当然,缺点也有:文档散落在WWDC视频里,社区案例少,遇到问题Stack Overflow上翻半天都找不到答案。但!只要摸清套路,它真的很稳。
实战:从0搭建一个UI测试用例
我们以“创建钱包”这个核心流程为例。用户点击“新建钱包” → 输入密码 → 确认 → 备份助记词 → 完成。整个流程涉及多个页面跳转、文本输入、按钮点击,还有一堆Toast提示。
首先,在Xcode里新建一个UI Test Target(别建错成Unit Test!)。然后写第一个测试:
import XCTest
class WalletCreationUITest: XCTestCase {
var app: XCUIApplication!
override func setUp() {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["UITesting"] // 关键!用于屏蔽弹窗或Mock数据
app.launch()
}
func testCreateWalletFlow() {
// 1. 点击“新建钱包”
app.buttons["newWalletButton"].tap()
// 2. 输入密码
let passwordField = app.secureTextFields["passwordInput"]
passwordField.tap()
passwordField.typeText("MySecurePass123!")
// 3. 确认密码
app.buttons["confirmPasswordButton"].tap()
// 4. 等待助记词页面出现(关键:加等待)
let mnemonicView = app.staticTexts["mnemonicTitle"]
XCTAssertTrue(mnemonicView.waitForExistence(timeout: 10))
// 5. 检查助记词是否显示
XCTAssertTrue(app.textViews["mnemonicWords"].exists)
// 6. 点击“完成”
app.buttons["finishButton"].tap()
// 7. 验证首页是否出现钱包列表
XCTAssertTrue(app.tables["walletList"].waitForExistence(timeout: 5))
}
}
踩坑记录
元素找不到?
XCTest默认用accessibilityIdentifier找元素,所以你的SwiftUI View里得加:Button("新建钱包") { ... } .accessibilityIdentifier("newWalletButton")别偷懒用label文字匹配,UI一改就挂。
异步操作没等完?
区块链操作常涉及网络请求或本地加密计算,UI会卡顿。必须用waitForExistence,别指望sleep(2)能解决问题——CI环境跑得快,本地慢,时间根本不可控。系统弹窗干扰?
比如通知权限、Face ID授权。解决方案:在launchArguments里传标志位,App启动时判断是否为测试环境,直接跳过授权逻辑。
和CI/CD集成:让测试真正跑起来
光本地跑通不够,得塞进流水线。我们用的是GitLab CI,配合fastlane。
Fastfile里加个lane:
lane :test_ios do
scan(
workspace: "MyApp.xcworkspace",
scheme: "MyAppUITests",
device: "iPhone 15",
code_coverage: true,
skip_build: false
)
end
然后在.gitlab-ci.yml里调用:
ios_test:
stage: test
script:
- bundle exec fastlane test_ios
only:
- merge_requests
这样,每次提PR都会自动跑UI测试。如果失败,直接挡住合并——再也不用担心“在我机器上是好的”这种鬼话了。
性能与资源消耗对比
为了说服导师和组里同学,我还做了个简单对比:
| 测试方式 | 单次耗时 | 维护成本 | 稳定性 | 覆盖场景 |
|---|---|---|---|---|
| 手动测试 | 8-10分钟 | 极高 | 低 | 全 |
| XCTest UI Test | 1.5分钟 | 中 | 高 | 核心路径 |
| 第三方框架(如Appium) | 2.5分钟 | 高 | 中 | 全 |
XCTest虽然不能覆盖所有边缘场景,但对核心业务流程的保障效率极高。而且,它不依赖外部服务,省下了服务器“资源”——要知道,我们实验室的经费可都是靠导师申请的,每一分钱都得花在刀刃上。
一点反思:自动化测试不是万能药
写完这套测试后,我一度以为问题解决了。结果上周五晚上,线上还是出了个Bug:用户在弱网下重复点击“创建钱包”,导致生成了两个相同地址的钱包。XCTest没测出来,因为它默认网络环境良好。
这让我意识到:自动化测试只是质量保障的一环。它适合验证“已知路径”,但防不住“未知异常”。所以现在我们的策略是:
- XCTest覆盖核心正向流程(占比70%)
- 手动探索性测试覆盖边界和异常(20%)
- 加上日志监控和崩溃上报(10%)
顺便吐槽一句:产品经理现在看到我说“加个测试用例”,眼睛都亮了,仿佛找到了免费劳动力……唉,研二狗的命运啊。
最后
XCTest可能不是最炫酷的工具,但它足够稳定、足够集成、足够Apple。对于像我们这种资源有限、又追求质量的学术+工程混合团队来说,它简直是救命稻草。
如果你也在做iOS开发,尤其是涉及金融、区块链这类高风险场景,别再手动点点了。花一天时间搭起XCTest框架,未来你会感谢现在的自己。
对了,下周我要开始研究如何用XCTest模拟区块链交易回滚——要是搞定了,再来更新。希望那时候不用再加班到凌晨三点 😅

评论 0