从零到一,我在真实项目中构建移动应用测试自动化的旅程

代码里的小宇宙
2025-06-14 22:38
阅读 224

开篇:为什么要写这篇文章?

开篇:为什么要写这篇文章?

作为一名在一线做移动端开发5年的工程师,我亲历了测试自动化从“可有可无”到“必不可少”的转变。尤其是近两年,随着团队规模扩大、产品迭代节奏加快,人工测试的瓶颈逐渐显现出来。我们开始意识到:仅靠 QA 流程和手动点测,已经无法支撑复杂功能持续交付的压力。

这篇文章并不是单纯介绍某个测试框架或工具,而是结合我在实际项目中的探索和踩坑经历,分享如何在一个已有一定体量的 App 上,逐步建立起一套可落地、可持续维护的移动应用测试自动化方案。希望我的经验能帮你少走弯路,快速上手。


背景:我们的项目痛点

背景:我们的项目痛点

事情要从2022年初说起,当时我所在的公司正在为一个拥有千万级用户的金融类App做重构升级。这个 App 最初是典型的“先上线再说”的产物,经过多年迭代,功能繁多且耦合严重。随着用户量上升和合规要求提升,频繁出现以下问题:

  1. 新功能回归测试耗时长:每次发版前需要几十个 QA 用例跑几轮,动辄十几个小时;
  2. 不同手机适配问题频出:安卓设备碎片化导致 UI 元素找不到、操作不一致;
  3. 测试覆盖率低,回归风险高:核心路径没完全覆盖,版本发布后常现严重 Bug;
  4. 人力成本高昂,效率低下:QA 同事每天大部分时间都在点按钮,缺乏创造性。

最严重的一次事故是在一次后台接口变更后,影响了一个涉及金额的展示模块,结果因为没人专门测试这个边缘路径,导致部分用户看到异常数据,引发了一波投诉。

于是,公司决定投入资源推进自动化测试体系建设。我作为 Android 技术负责人,被安排主导这块工作。


搭建之初的选择与迷茫

一开始我也很懵,毕竟之前只是听说过一些名词,比如 Espresso、UI Automator、Appium、Robot Framework、XCTest 等等。但真正要在生产环境里搭建起来,并不是照着教程跑几个 Demo 就行的。

当时我们遇到的第一个选择题是:

“我们到底要做什么类型的测试?”

明确目标:我们想要的测试自动化是什么样的?

经过与产品经理、测试负责人一起讨论后,我们定下两个明确的目标:

  1. 保证核心流程稳定可靠(如登录、注册、交易下单、支付、账单查看等);
  2. 减少重复性回归测试工作,让测试人员专注探索式测试和边界场景挖掘

因此我们主要聚焦在 UI 层面的端到端测试自动化(E2E),而非单元测试或者本地仪器化测试。虽然它们也重要,但对当前团队来说,UI 自动化带来的收益更直接明显。


我们最终的技术选型和实现思路

基于项目现状、人员构成和平台覆盖需求(主要是 Android 和少量 iOS 用户),我们选择了如下技术栈:

平台 工具
Android Espresso + UIAutomator
iOS XCTest + XCUITest
统一脚本管理 & 执行 Python + Pytest + Allure 报告生成器
CI/CD 集成 Jenkins + GitLab CI

这种混合架构的好处是充分利用各自平台原生支持的能力,同时通过统一的调度系统来管理用例执行。

我们还搭建了一个轻量的调度服务,用来管理不同机型、执行任务分配和失败重试机制。


实战演练:编写一个登录流程的测试用例

为了让大家更有代入感,下面我会以我们早期的一个真实测试用例为例,说明整个流程是如何组织的。

这个例子对应的是 Android 的登录功能,假设我们需要验证输入手机号和密码是否成功登录。

@RunWith(AndroidJUnit4::class)
class LoginTest {

    @get:Rule
    val activityScenarioRule = ActivityTestRule(LoginActivity::class.java)

    @Test
    fun testValidLogin() {
        // 输入手机号
        onView(withId(R.id.et_phone))
            .perform(typeText("13812345678"), closeSoftKeyboard())

        // 输入密码
        onView(withId(R.id.et_password))
            .perform(typeText("password123"), closeSoftKeyboard())

        // 点击登录按钮
        onView(withId(R.id.btn_login)).perform(click())

        // 验证跳转后的首页标题是否显示正确
        onView(withId(R.id.tv_title))
            .check(matches(withText("首页")))
    }
}

上面的代码虽然简单,但在初期其实有很多“坑”是我们一步步踩过来的:


踩坑记:那些让我夜不能寐的问题

1. UI元素找不到,定位不稳定

我们最初写的测试用例,在某些机器上总是报 NoMatchingViewException,后来发现是因为:

  • 布局层级嵌套深,ID 不唯一;
  • 动画还没结束就进行点击,页面未加载完成;
  • 页面异步加载导致控件尚未绘制完毕。

解决办法:

  • 使用 onView().inRoot() 来精准指定 RootWindow。
  • 使用 IdlingResource 等待网络请求或动画结束。
  • 加入等待条件的封装函数:
fun waitUntilViewMatch(matcher: Matcher<View>, timeout: Long = 10_000L) {
    val startTime = System.currentTimeMillis()
    while (System.currentTimeMillis() - startTime < timeout) {
        try {
            onView(matcher).check(matches(isDisplayed()))
            return
        } catch (e: NoMatchingViewException) {
            Thread.sleep(500)
        }
    }
    throw TimeoutException("Timed out waiting for view match.")
}

2. 跨设备兼容问题

我们在小米、OPPO、华为等不同品牌手机上运行测试用例时,经常遇到:

  • 键盘弹出方式不一样;
  • Toast 或 Dialog 出现位置不确定;
  • 特定机型的权限弹窗阻断后续操作;
  • 有些厂商定制了系统组件,导致 UIAutomator 定位失败。

解决方法:

  • 对常用 UI 元素尽量使用自定义的 Content Description 标识;
  • 在 Jenkins 中配置多种机型真机并行执行,观察行为差异;
  • 对于权限弹窗这类“非预期”交互,引入 UIAutomator 进行全局监控,处理系统级别的弹窗:
public void handlePermissionDialogs() throws UiObjectNotFoundException {
    UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    if (device.hasObject(By.text("允许"))) {
        device.findObject(By.text("允许")).click();
    }
}

3. CI 构建失败率高

由于 Jenkins 是基于模拟器跑的测试,有时候会出现莫名其妙的失败:

  • 网络延迟导致接口超时;
  • CPU 占用过高导致模拟器卡顿;
  • 多个用例之间状态残留,互相干扰。

改进措施:

  • 将重点用例优先放在真机池中执行;
  • 每个用例执行前重置 App 数据;
  • 使用 Test Orchestrator 分离每个测试的运行上下文;
  • 增加失败重试逻辑,避免偶发性问题影响报告判断。

持续集成与报告可视化

我们使用的 CI 流水线大致如下:

stages:
  - build
  - test_ui
  - report

build_app:
  script:
    - ./gradlew assembleDebug
    - cp app-release.apk $ARTIFACT_DIR/

run_android_tests:
  script:
    - ./gradlew connectedAndroidTest
    - cd app/build/outputs/reports/androidTests/connected/
    - mv results/ ../../../test_results/

generate_report:
  script:
    - pip install allure-pytest
    - allure serve $TEST_RESULTS_DIR

借助 Allure 的能力,我们可以非常直观地看到测试执行的时间趋势、用例成功率、失败截图等信息,极大提升了问题排查效率。


效果总结:我们收获了什么?

半年过去后,回过头来看我们建立的这套自动化体系,带来了几点明显的收益:

  • 节省人力投入:UI 回归测试由原来的 8 小时压缩至 40 分钟,且可在夜间自动执行;
  • 提高发布信心:主流程测试覆盖率超过 80%,关键功能变更都能第一时间捕获潜在问题;
  • 反向驱动代码质量:为了写出稳定易维护的测试代码,我们也逐步规范了代码结构,提高了可测试性;
  • 推动团队协作:测试人员和开发共同参与自动化建设,形成闭环反馈机制。

此外,我们还在每次提审各大应用商店前,加入自动化测试环节,有效减少了因基本路径错误导致的审核驳回。


给你的建议:别怕,大胆迈出第一步

如果你现在正打算着手自动化测试,又不知道从哪里下手,我可以分享几点实战中总结的经验:

✅ 从关键路径入手,不要追求大而全

  • 优先覆盖登录、支付、核心业务流;
  • 不要一开始就想着把所有用例都自动化。

✅ 让测试人员参与进来,培养“测试工程师”角色

  • 开发写底层能力,测试写用例逻辑;
  • 用 Pytest 或 JUnit 组织用例目录,清晰易读。

✅ 注重可维护性,设计好 Page Object 模式

  • 类似 Web 测试的 Page Object,在移动端也同样适用;
  • 每个页面抽象成对象,降低后期维护成本。

✅ 重视报告与可视化,让成果“被看见”

  • Allure、ExtentReport 等开源工具足以满足中小型团队需求;
  • 把测试结果融入发布看板,增强可信度。

✅ 拒绝“为测而测”,要服务于质量提升

  • 自动化只是手段,不是目的;
  • 结合人工探索测试,才能更好发现问题。

写在最后:关于未来的一些思考

今年我在社区也看到很多同学在尝试使用 AI 辅助测试、图像识别比对界面、行为录制生成脚本等功能,甚至已经有公司在做类似“智能测试助手”的产品。

但我始终觉得,任何技术工具都代替不了我们对产品的深刻理解和工程上的扎实实践。自动化测试的核心,永远是人。它是一门工程学,而不是简单的“写个脚本跑一遍”。

所以我想对大家说:“如果你刚开始,别怕。从你最熟悉的那一块开始,慢慢摸索,终将收获一份安心。”

这趟旅程我走了很久,踩了很多坑,但也收获了成长。希望这篇来自实战的文字,对你有点帮助。


作者简介
一线移动开发者,热爱编码与工程实践,曾在多个大型 App 项目中负责自动化测试体系建设,热衷于分享真实项目经验与技术落地心得。欢迎关注我的 GitHub(此处可替换为你的主页链接),一起探讨更多移动开发实战话题。

评论 0

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