从加班写测试到用XCTest自动跑:一个考公程序员的iOS自动化自救指南
上周五晚上十点,我盯着屏幕上密密麻麻的UI操作步骤文档,手边的冰美式已经凉透。产品经理刚发来第7版“紧急上线”需求,要求下周三前完成全部回归测试——而我们组唯一的测试同事正在休婚假。
那一刻我真的想砸电脑。
但转念一想,不行,这台MacBook Pro是我攒了半年工资买的,砸了心疼。而且再过三个月就要参加省考了,得稳住心态。于是深吸一口气,打开终端,决定把去年双11就该搞的iOS自动化测试捡起来。
作为一名在互联网公司卷了三年多的前端程序员(对,就是那个天天被说“切页面的”岗位),我对交互和动画一直比较上头。但说实话,写测试脚本这事,我一直拖着没碰——总觉得这是QA的事,跟我这个“创造者”没啥关系。直到最近项目频繁线上bug,老板在周会上阴阳怪气地说“你们开发是不是觉得自己写的代码永远不出错?”,我才意识到:自动化测试不是负担,是保护自己的盔甲。
为什么选XCTest?
其实市面上自动化方案不少:Appium、EarlGrey、KIF……但我最终还是回到了Apple亲儿子XCTest。原因很简单:
- 原生支持,跟Xcode无缝集成
- 写Swift的同学不用额外学新语言
- 审核时不会因为用了第三方测试框架被拒(吃过亏)
- 对SwiftUI的支持越来越好了
最关键的是——我不想再装一堆Java环境、Node.js依赖了。在Mac上敲代码已经够清爽了,干嘛非要用Windows那一套笨重的东西?
上手XCTest:比想象中简单
新建一个iOS项目后,Xcode默认就会带一个YourAppUITests target。进去一看,模板代码长这样:
import XCTest
class YourAppUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
XCUIApplication().launch()
}
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
}
别被XCUIApplication()吓到,它其实就是你正在测试的那个App实例。你可以像操作真实用户一样去点按钮、输文字、滑动页面。
举个实际例子:我们有个登录页,需要输入手机号和验证码。手动测一次就得30秒,一天测20轮?命都搭进去了。
于是我写了这段自动化脚本:
func testLoginFlow() {
let app = XCUIApplication()
// 等待登录页出现
let phoneField = app.textFields["phoneInput"]
XCTAssertTrue(phoneField.waitForExistence(timeout: 5))
// 输入手机号
phoneField.tap()
phoneField.typeText("13800138000")
// 点击获取验证码(假设按钮accessibilityIdentifier是"getVerifyCode")
app.buttons["getVerifyCode"].tap()
// 等待验证码输入框可编辑
let codeField = app.textFields["verifyCode"]
XCTAssertTrue(codeField.waitForExistence(timeout: 3))
codeField.tap()
codeField.typeText("123456")
// 点击登录
app.buttons["loginButton"].tap()
// 验证是否跳转到首页(比如首页有个tab叫"homeTab")
XCTAssertTrue(app.tabBars.buttons["homeTab"].waitForExistence(timeout: 10))
}
关键点来了:一定要给UI元素设置accessibilityIdentifier!
别偷懒用label或placeholder去定位,那些文字可能变,但identifier是你自己定的,稳如老狗。
// 在你的SwiftUI View里这么写
TextField("请输入手机号", text: $phoneNumber)
.accessibilityIdentifier("phoneInput")
Button("获取验证码") {}
.accessibilityIdentifier("getVerifyCode")
踩过的坑:那些让我半夜惊坐起的瞬间
坑1:异步操作等不到
第一次跑的时候,验证码按钮点了没反应。查了半天发现网络请求是异步的,脚本执行太快,还没收到响应就往下走了。
解决方案:用XCTWaiter或者更简单的——加等待:
let expectation = XCTestExpectation(description: "等待验证码发送成功")
// 模拟网络回调后 fulfill expectation
wait(for: [expectation], timeout: 10)
但说实话,这种写法太啰嗦。后来我学会了用waitForExistence配合合理的timeout,大部分场景够用了。
坑2:真机 vs 模拟器行为不一致
有次在模拟器跑得好好的,一上真机就崩。原因是模拟器键盘是软键盘,而真机连了蓝牙键盘……导致typeText失效。
解决方法:统一用setValue直接设值,绕过键盘输入:
phoneField.setValue("13800138000", for: .value)
不过要注意,这招只适用于测试,不能用于生产代码(会被App Store拒)。
坑3:测试数据污染
最开始我把测试账号写死在脚本里,结果某天产品改了登录逻辑,所有测试全挂。后来学乖了——用独立的测试环境 + 动态生成测试账号。
现在我们的CI流程里会先调一个内部接口创建临时用户,拿到token后再跑UI测试,彻底隔离生产数据。
AI提效:别傻傻手写所有用例
说到这儿,必须提一句:别试图手写100%的测试用例。我之前就是太较真,想覆盖所有路径,结果写了两周还在搭框架。
后来试了几个AI工具,发现真香:
- MCP(Mobile Cloud Platform):我们公司自研的移动测试平台,能自动录制用户操作并生成XCTest脚本。虽然生成的代码有点啰嗦,但至少省了80%的体力活。
- Moltbot:一个开源的iOS测试辅助工具,可以智能识别页面元素并推荐accessibilityIdentifier命名,对我这种命名困难症太友好了。
- 文心一言:别笑!有次我卡在一个手势操作上(需要双指缩放地图),直接把报错信息贴给文心一言,它居然给出了正确的
XCUIElement手势API用法,比翻Apple文档快多了。
当然,AI不是万能的。它生成的代码往往缺乏边界条件处理,比如网络超时、弹窗拦截等。但作为初稿,效率提升非常明显。我现在的工作流是:AI生成骨架 → 手动补充异常处理 → 加入断言验证。
实战效果:从每天2小时到5分钟
这套自动化跑起来后,最直观的变化是——我不用再周末加班测回归了!
以前每次发版前,光登录、注册、支付这几个核心路径就得手动跑一个多小时。现在CI流水线里加一行:
xcodebuild test -workspace YourApp.xcworkspace -scheme YourApp -destination 'platform=iOS Simulator,name=iPhone 15' -enableCodeCoverage YES
5分钟后,测试报告自动发到钉钉群,附带截图和失败原因。老板看了都说“这届程序员终于开窍了”。
更重要的是,线上P0级bug少了60%。那些“只在特定机型出现”的玄学问题,现在都能在合并代码前被自动化拦住。
给想上岸程序员的一点真心话
我知道很多人觉得:“我都准备考公了,还折腾这些干啥?”
但我想说:技术能力是你最后的退路。万一笔试没过,或者面试被刷,你总得有口饭吃。而且公务员考试越来越卷,很多岗位明确要求“有软件开发经验”,你这些实战项目,写在简历上就是加分项。
再说了,搞自动化测试这件事本身,就是在训练你的系统性思维——怎么设计用例?如何解耦依赖?怎样保证稳定性?这些能力,不管是写代码还是做行政,都用得上。
所以别躺平,哪怕每天只花半小时,把重复劳动自动化掉。省下来的时间,既可以刷行测题,也能陪对象看场电影。何乐不为?
最后附一张我们团队现在的测试覆盖率对比(纯文字版,毕竟不能插图):
| 测试类型 | 自动化前 | 自动化后 | 提升 |
|---|---|---|---|
| 核心路径UI测试 | 30% | 95% | +65% |
| 回归测试耗时 | 2h/次 | 8min/次 | -93% |
| 线上严重bug | 4.2个/月 | 1.7个/月 | -59% |
数据不会骗人。如果你也在被重复测试折磨,不妨试试XCTest。它可能不是最强大的,但对Apple生态开发者来说,足够好用,足够省心。
好了,写完这篇博客,我也该去刷申论了。希望明年此时,我已经坐在体制内的工位上,悠闲地喝着保温杯里的枸杞茶——但电脑里,依然跑着整洁高效的代码。

评论 0