iOS自动化测试没那么可怕,XCTest真香

杨磊_工程师
2026-01-15 09:55
阅读 280

去年双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))
    }
}

踩坑记录

  1. 元素找不到?
    XCTest默认用accessibilityIdentifier找元素,所以你的SwiftUI View里得加:

    Button("新建钱包") { ... }
        .accessibilityIdentifier("newWalletButton")
    

    别偷懒用label文字匹配,UI一改就挂。

  2. 异步操作没等完?
    区块链操作常涉及网络请求或本地加密计算,UI会卡顿。必须用waitForExistence,别指望sleep(2)能解决问题——CI环境跑得快,本地慢,时间根本不可控。

  3. 系统弹窗干扰?
    比如通知权限、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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝