外包干了3年终于跳进甲方的Java开发,但我今天想聊聊iOS自动化测试

Bean没注入
2026-07-10 03:06
阅读 447

作者:一个在老家远程办公、省了房租的Java老兵 时间:2024年某个深夜,刚把娃哄睡


一、开篇:那个让我破防的深夜

上周五晚上十一点半,我坐在老家三线城市那套89平的小三房书房里,面前摆着一杯凉透的美式,屏幕上是甲方项目组发来的性能压测报告。

iOS端的UI自动化测试用例跑了整整47分钟,CI流水线卡得像便秘,测试组的兄弟在钉钉群里@我:"哥,这 XCTest 跑得比外包还慢,能不能优化一下?"

我盯着屏幕,脑子里闪过三年前那个画面——

2021年10月,深圳南山某栋写字楼,我坐在外包工位上,旁边是一个比我小三岁的甲方正式员工,他随手改了一行代码就能合进主干,而我改个按钮颜色都要提MR、等审批、走流程。那时候我月薪15K,房租3500,每天挤4号线上班,晚上回到城中村那个15平的隔断间,连个像样的书桌都没有。

当时真的很焦虑。不是那种"我要努力"的焦虑,是那种"我是不是这辈子就这样了"的焦虑。

现在呢?去年三月跳进甲方,月薪从15K涨到了22K,最爽的是跟领导申请了远程办公,每个月省下的房租够我买两台机械键盘。但说实话,跳进来之后才发现,甲方也不是天堂,该卷还是卷,该背锅还是背锅,只不过从"外包背锅侠"变成了"正式工背锅侠"。

但今天这篇文章,我不想聊跳槽,不想聊薪资,我想聊一个让我最近头秃的技术话题——iOS自动化测试中的XCTest框架,以及我是怎么用性能优化的思路,把那个跑了47分钟的测试套件干到11分钟的。

你可能会问:你一个Java开发,写什么iOS?

别急,听我慢慢说。


二、事情是这样的

先交代一下背景。

我们组是个全栈团队,后端用Spring Cloud微服务,前端有Web、小程序,还有iOS和Android客户端。后端团队6个人,iOS就2个,Android就1个。去年十月,iOS的那个老哥离职了,走之前留下一坨跑了快一年的XCTest自动化测试代码。

这坨代码有多烂呢?我给你描述一下:

// 某个测试用例,别问为什么这么长,问就是历史遗留
func testLoginFlow() {
    let app = XCUIApplication()
    app.launch()
    
    // 等待启动广告,硬编码等5秒
    sleep(5)
    
    // 点击"跳过"按钮
    let skipButton = app.buttons["跳过"]
    if skipButton.waitForExistence(timeout: 10) {
        skipButton.tap()
    }
    
    // 又硬编码等3秒
    sleep(3)
    
    // 输入手机号
    let phoneField = app.textFields["请输入手机号"]
    phoneField.tap()
    phoneField.typeText("13800138000")
    
    // 再等2秒
    sleep(2)
    
    // 点击获取验证码
    app.buttons["获取验证码"].tap()
    
    // 又又硬编码等5秒
    sleep(5)
    
    // ...后面还有20多行类似的代码
}

看到没?满屏的 sleep(),硬编码等待时间,整个测试套件跑下来,光等就等了二十多分钟。再加上各种UI元素的查找和交互,47分钟都算快的了。

测试组的兄弟跟我说:"哥,我们每次提测,CI跑一遍iOS的自动化回归,得等将近一个小时。有时候跑着跑着还超时失败了,重新跑又得一个小时。这谁顶得住啊?"

我当时内心OS:这谁写的代码?这他妈写的是测试还是写的催眠曲?

但吐槽归吐槽,活还是得干。谁让我是组里唯一"懂点iOS"的后端呢(别问为什么后端要懂iOS,问就是小公司没人)。


三、XCTest框架,到底是个啥

在动手优化之前,我得先搞清楚 XCTest 这玩意儿到底是怎么回事。

说实话,我一开始是懵的。写了七八年Java,JUnit、TestNG、Mockito用得飞起,但iOS的测试框架我是真没怎么接触过。于是那个周末,我泡了两天的官方文档,外加翻了Stack Overflow上几十个高赞回答,总算搞明白了。

3.1 XCTest 的核心架构

XCTest 是 Apple 官方的测试框架,从 Xcode 诞生之日起就跟着了。它主要分三大块:

  1. XCTestCase:测试用例的基类,类似于 JUnit 的 @Test 方法。你写的每一个测试方法,都得继承这个类。
  2. XCUIApplication:代表你要测试的App实例。通过它可以启动App、发送事件、查询UI元素。
  3. XCUIElement:代表UI上的一个元素,比如按钮、文本框、列表等。你通过它来进行点击、输入、滑动等操作。
class LoginTests: XCTestCase {
    var app: XCUIApplication!
    
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()
    }
    
    override func tearDown() {
        app.terminate()
        super.tearDown()
    }
    
    func testLoginSuccess() {
        // 测试逻辑
    }
}

看起来很简单对吧?但魔鬼在细节里。

3.2 性能瓶颈在哪

我把那坨代码从头到尾看了一遍,又用 Instruments 跑了几个 Profile,发现性能瓶颈主要集中在以下几个方面:

第一,滥用 sleep() 进行硬等待。 这是最大的问题。sleep(5) 就是实打实地等5秒,哪怕UI元素1秒就加载完了,你也得傻等5秒。整个测试套件里,光 sleep() 加起来就等了22分钟。

第二,重复启动App。 每个测试用例的 setUp() 里都调用了 app.launch(),而 tearDown() 里又调用了 app.terminate()。一个测试套件50多个用例,App启动+销毁就来了50多次,每次启动至少3-5秒。

第三,UI元素查找策略低效。 代码里大量使用 app.buttons["xxx"] 这种基于文本的查找方式,而很多按钮的文本是动态的,导致查找超时。

第四,没有利用并行测试。 XCTest 从 Xcode 9 开始就支持并行测试了,但这坨代码完全是串行的。

找到问题就好办了。接下来就是优化。


四、性能优化实战

4.1 干掉所有 sleep(),用显式等待替代

这是收益最大的一步。我把所有的 sleep() 全部替换成了 waitForExistence(timeout:) 这种显式等待方式。

// 优化前
sleep(5)
let skipButton = app.buttons["跳过"]
skipButton.tap()

// 优化后
let skipButton = app.buttons["跳过"]
XCTAssertTrue(skipButton.waitForExistence(timeout: 5), "跳过按钮未在规定时间内出现")
skipButton.tap()

区别在哪?sleep(5) 是死等5秒,而 waitForExistence(timeout: 5)最多等5秒,元素一出现就立刻往下走。如果元素1秒就出来了,那就只等1秒。

光这一步,整个测试套件的时间就从47分钟降到了28分钟。省了将近一半。

4.2 减少App重启次数

我把 app.launch() 从每个用例的 setUp() 里移了出来,改成了在整个测试类启动时只执行一次。对于那些确实需要干净环境的用例,用 launchArguments 来传递参数,而不是重启App。

class LoginTests: XCTestCase {
    static var app: XCUIApplication!
    
    override class func setUp() {
        super.setUp()
        app = XCUIApplication()
        app.launchArguments = ["--uitesting"]
        app.launch()
    }
    
    override func setUp() {
        super.setUp()
        // 不再重启App,而是通过导航回到初始状态
        navigateToLoginScreen()
    }
}

这里有个小技巧:在 AppDelegate 里检测 --uitesting 这个启动参数,如果存在就跳过启动广告、跳过引导页,直接进入主界面。这样每次测试的起点就更快了。

// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool {
    if ProcessInfo.processInfo.arguments.contains("--uitesting") {
        // 跳过广告、引导页等
        skipOnboarding()
    }
    return true
}

这一步又省了大约8分钟。

4.3 优化UI元素查找

我把基于文本的查找方式,尽量换成了基于 accessibilityIdentifier 的查找。accessibilityIdentifier 是专门给自动化测试用的标识符,不会像文本那样因为多语言、动态内容而变化。

// 优化前:基于文本查找,慢且不稳定
let loginButton = app.buttons["登录"]

// 优化后:基于accessibilityIdentifier查找,快且稳定
let loginButton = app.buttons["login_button"]

同时在业务代码里给关键UI元素加上 accessibilityIdentifier

// LoginViewController.swift
loginButton.accessibilityIdentifier = "login_button"
phoneTextField.accessibilityIdentifier = "phone_input"

这一步对稳定性的提升比性能更大,但确实也减少了一些因为查找超时导致的等待时间。

4.4 开启并行测试

XCTest 支持在多个模拟器上并行运行测试。我在 CI 配置里开启了这个特性:

# Fastlane / CI 配置
scan(
  scheme: "MyApp",
  destination: "platform=iOS Simulator,name=iPhone 15",
  parallel_testing: true,
  concurrent_workers: 4
)

50多个用例分配到4个并行worker上,理论上能快4倍。实际跑下来,因为有些用例之间有依赖关系不能并行,最终整体时间又缩短了大约40%。

4.5 最终结果

经过这一系列优化,最终的效果:

指标 优化前 优化后 提升
总执行时间 47分钟 11分钟 76%
失败率 23% 3% 87%
平均单用例时间 56秒 13秒 77%

测试组的兄弟看到结果之后,在群里发了个大拇指:"牛逼,终于不用等一个小时了。"

那一刻,说实话,还是挺有成就感的。虽然我是个写Java的,但把iOS的测试性能优化搞定了,这种感觉就像你本来只会做川菜,突然有一天把法餐也给整明白了。


五、一些题外话:关于 Lovable、后端、AI视频和v0

写到这里,本来这篇文章就该结束了。但最近圈子里几个东西挺火的,我想顺便聊聊,算是夹带点私货。

5.1 Lovable:低代码的又一个尝试

上个月有个朋友给我推了个叫 Lovable 的工具,说是"AI驱动的全栈开发平台"。我花了一个下午试了试,怎么说呢,体验有点像当年的 v0,但更偏向非技术人员。

你可以用自然语言描述你想要的应用,它会帮你生成前端页面、后端接口,甚至连数据库都给你建好。对于快速验证想法来说,确实挺方便的。

但作为一个干了七八年开发的老兵,我的看法是:这类工具适合做原型和MVP,但真正要上生产环境,还是得靠专业的工程师来打磨。 就像我优化XCTest一样,AI可以帮你生成测试代码,但它不知道你的业务逻辑里哪些地方有坑,哪些等待时间是必须保留的。

5.2 后端开发的未来

说到 后端,最近ChatGPT、Claude这些AI工具对后端开发的冲击确实挺大的。以前写个CRUD接口,从建表到写Controller到写Service到写DAO,一套下来怎么也得两三个小时。现在用AI辅助,半小时搞定,代码质量还不差。

但这并不意味着后端开发会消失。恰恰相反,越是在AI能帮你写代码的时代,架构设计、性能优化、系统稳定性这些"高级技能"就越值钱。 就像我这次优化XCTest,AI可以帮你把 sleep() 替换成 waitForExistence(),但它不会主动去分析整个测试套件的性能瓶颈在哪,更不会去设计一个 --uitesting 的启动参数来跳过广告。

这些,还是需要人来思考。

5.3 AI视频:下一个风口?

最近 AI视频 赛道太火了,Sora、可灵、Runway,一个比一个猛。说实话,作为一个技术人,我看这些工具的第一反应不是"哇好厉害",而是"这背后的推理成本得有多高"。

生成一段10秒的视频,背后可能是几百张A100在跑。这种算力消耗,短期来看,商业化路径还不清晰。但长期来看,AI视频一定会改变内容创作的方式,就像当年短视频改变了媒体行业一样。

不过这些跟我一个写Java的有什么关系呢?关系就是:技术趋势的变化,会倒逼我们不断学习新东西。 也许明年,我们组的iOS测试就不是写XCTest了,而是用AI直接生成测试用例、自动执行、自动分析失败原因。到那时候,我现在优化的这些性能技巧,可能就成了"上古时代的遗产"。

5.4 v0:前端开发的AI化

最后说说 v0。Vercel 出的这个AI前端生成工具,我上个月用来快速搭了个内部运营后台的页面。效果确实惊艳,给个描述就能生成一个看起来还不错的React页面,而且代码结构也挺规范的。

但同样的问题:生成的代码缺乏业务深度。它不知道你的用户画像,不知道你的业务场景,不知道哪些交互细节是需要打磨的。AI是工具,不是替代品。 这句话虽然老套,但真的是真理。


六、回到正题:一些感悟

好了,私货夹完了,回到正题。

从外包跳到甲方这一年多,我最大的感悟是什么?

不是薪资涨了,不是不用挤地铁了,而是我终于有了" ownership"。

在外包的时候,代码写得再烂我也不心疼,反正不是我的项目,改完就走。但在甲方,你写的每一行代码、做的每一次优化,都会影响到真实的用户、真实的业务。那种"这是我的东西,我要把它做好"的感觉,是外包给不了的。

就像这次优化XCTest,如果是在外包公司,我大概率会跟领导说"这是iOS的活,跟我没关系"。但在甲方,测试跑不动影响的是整个团队的效率,而团队效率影响的是项目进度,项目进度影响的是大家的年终奖。所以你得管,哪怕你不是iOS开发。

技术人的价值,不在于你会什么语言、什么框架,而在于你能不能解决问题。

你会Java,但你愿意去优化iOS的测试性能,这就是价值。你会写CRUD,但你愿意去研究AI工具怎么提升团队效率,这也是价值。


七、给同行的一些建议

最后,给跟我一样从外包出来的兄弟们几点建议:

  1. 不要只盯着技术栈。 语言、框架这些东西,学起来没那么难。重要的是解决问题的能力和思维方式。
  2. 主动承担"分外之事"。 在甲方,你多做一点,别人就多看 you 一眼。机会都是这么来的。
  3. 保持学习的习惯。 不管是 XCTest 还是 Lovable、AI视频、v0,新东西出来就去了解一下,不一定要精通,但要知道是怎么回事。
  4. 关注性能,关注细节。 很多初级开发写代码只关注"能不能跑",但真正的高级开发关注的是"跑得快不快、稳不稳"。这次XCTest优化就是最好的例子。
  5. 别怕跨领域。 你是Java开发,不代表你不能优化iOS测试。技术是相通的,性能优化的思路在哪里都一样。

八、写在最后

写完这篇文章的时候,已经凌晨一点半了。隔壁房间传来老婆和娃的呼吸声,书房里只有键盘的敲击声和窗外偶尔经过的电动车声。

在老家远程办公的这一年,是我职业生涯里最踏实的一年。不用挤地铁,不用租隔断间,每天下班就能陪娃玩一会儿。薪资虽然不算高,但在三线城市,22K的月薪加上省下来的房租,日子过得还是挺舒服的。

但我心里清楚,这种舒服不能持续太久。技术行业变化太快了,今天你会的东西,明天可能就被AI替代了。所以哪怕现在日子过得不错,该学习还是得学习,该折腾还是得折腾。

就像优化XCTest一样,永远没有"最优解",只有"更优解"。

共勉。


如果这篇文章对你有帮助,点个赞再走吧。也欢迎在评论区聊聊你的外包经历,或者你对XCTest、AI工具的看法。

我是那个从外包跳进甲方的Java开发,现在在老家远程办公,省了房租,但没省掉对技术的热爱。

我们下篇文章见。

评论 0

最热最新
暂无评论
Bean没注入Lv.1
0
影响力
0
文章
0
粉丝