移动应用测试自动化实践:从“人肉点”到“自动跑”的那些事儿
开篇:从零开始,为什么我们非得搞自动化?

我至今还记得那年冬天,团队开发了一款面向企业用户的跨平台移动应用。当时项目刚进入上线前的冲刺阶段,每次发版都伴随着大量的手动回归测试工作,每天早上第一件事就是打开手机,一个界面一个界面地点按钮、看日志、录视频……整个人就像被绑在了“点点点”的机器上。
有一次版本迭代比较频繁,一天提测两次,那天晚上我加班到凌晨三点才搞定当天的测试任务,第二天早上发现又有一个紧急Hotfix需要验证。那种身心俱疲的感觉让我意识到一个问题:如果继续靠人肉来做测试,这条路是走不通的。
于是我们开始推动测试自动化的落地。但说实话,过程远没有想象中顺利。
问题描述:测试自动化不是加个脚本就完事


一开始我们都天真地认为:“不就是写个脚本来模拟点击吗?找几个开源框架一搭,应该很简单。”
但现实狠狠打了我们一巴掌。
我们的应用同时支持 Android 和 iOS,UI 层使用的是 React Native,底层通信依赖原生模块,接口还涉及到一些旧系统改造后的遗留接口。这就导致我们在做自动化的时候,遇到了几个非常棘手的问题:
- 跨平台适配差:Android 和 iOS 的元素定位方式不同,代码无法复用。
- 控件识别不稳定:React Native 渲染出来的 View 没有明确的 resource-id,Appium 定位困难。
- 网络环境复杂:有些接口请求失败或者慢加载,导致自动化脚本经常失败。
- CI 集成成本高:本地运行没问题,一上 CI 就各种出错,甚至找不到设备。
- 缺乏统一规范:前端没给控件打 tag,写脚本全靠猜 ID,维护起来极其痛苦。
最离谱的一次,我们写了个 UI 测试脚本跑了三天,结果因为某个页面换了个图标,整个流程就卡死了……
解决方案:选型+规范+平台化三管齐下

为了解决这些问题,我们并没有急于上代码,而是先做了几件关键的事。
第一步:技术选型稳扎稳打
经过多次讨论和技术调研,我们决定采用以下组合:
- 框架层:Appium + WebdriverIO(支持多语言,社区活跃)
- 测试语言:JavaScript/TypeScript(与前端技术栈统一,降低学习成本)
- 持续集成工具:GitHub Actions + Docker
- 云测平台辅助:初期使用 AWS Device Farm 做兼容性测试
- 日志和报告:Allure + Mochawesome Report
第二步:建立控件命名规范
这是我们吃过最大的亏之一。后来我们和前端达成共识:
- 所有可交互控件必须加上
testID,格式统一为<pageName>_<element>,例如login_button_submit - 页面切换时触发埋点日志,方便调试时判断是否成功跳转
- 给每个页面加全局唯一的标识字段,作为测试脚本识别当前页面状态的依据
这个规范在后续脚本编写过程中起到了至关重要的作用。
第三步:构建公共函数库和封装方法
为了减少重复劳动和提高维护效率,我们封装了一系列常用的工具函数,比如:
- 等待控件出现并点击
- 获取 Toast 提示内容
- 处理系统权限弹窗
- 判断当前网络状态
举个例子:
// utils/waitUtil.js
async function waitForElementAndClick(selector, timeout = 10000) {
try {
const el = await $(selector);
await el.waitForDisplayed(timeout);
await el.click();
} catch (error) {
throw new Error(`Failed to wait and click element: ${selector}`);
}
}
module.exports = { waitForElementAndClick };
通过这种方式,我们的核心业务流程测试脚本逐渐变得清晰、简洁,也更容易被其他成员接手维护。
踩坑经验:你以为的稳定其实很脆弱

在实际操作过程中,有很多“细节中的魔鬼”,踩过的坑现在回忆起来都觉得头皮发麻。
控件找不到,怎么办?
React Native 的组件往往没有 id,只能依赖文本或类名来找,这会导致识别率低下。我们最终采取了两个策略:
- 强制要求前端加上 testID,作为唯一定位标准。
- 对于动态生成的内容(如列表),采用 XPath 表达式结合部分匹配查找。
比如这段动态生成的用户列表:
// 使用 XPath 查找昵称为 "Tom" 的用户
const userCell = await $('//android.widget.TextView[contains(@text, "Tom")]/parent::*/parent::*');
await userCell.click();
虽然不太优雅,但在没有唯一id的情况下,这是可行的折中方案。
Appium连接断开,设备突然掉线?
这个问题在早期特别频繁。后来我们总结了几点经验:
- 不要过度依赖 USB 设备连接,优先使用无线调试配合 CI。
- 在 GitHub Actions 中,我们搭建了一个基于 Docker 的 Appium Server 环境,并使用安卓模拟器。
- 对于某些奇怪的崩溃问题,可以考虑加一层重试逻辑:
async function retry(fn, retries = 3, delay = 3000) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (e) {
if (i === retries - 1) throw e;
console.log(`Retry ${i + 1}:`, e.message);
await browser.pause(delay);
}
}
}
效果总结:效率提升看得见
实施自动化之后,我们明显感觉到了几个方面的改善:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 回归测试耗时 | 2小时以上 | 20分钟以内 |
| 版本发布准备时间 | 至少半天 | 数分钟即可启动测试 |
| bug漏测率 | 约15% | 下降至3%以内 |
| 成员参与度 | 只有测试同学做 | 前端、QA、DevOps 共同维护 |
除了这些数据指标上的提升,更重要的是建立了良好的协作氛围。大家开始重视“可测试性设计”,从前端到后端都有了“要让别人好测试”的意识。
经验分享:几点建议送给正在路上的你
1. 不要急着写第一个测试用例
先定规范,再搭框架,否则后期维护会非常麻烦。
2. 把自动化当成产品来看待
自动化不是一次性的任务,它需要持续维护和改进。要有监控、有报警、有问题追踪机制。
3. 优先覆盖核心路径
刚开始不必追求全覆盖,先抓住最关键的路径。比如登录、下单、支付这些流程,优先保证稳定性。
4. 性能和体验也是测试的一部分
别忘了,测试不仅仅是为了功能正确,还包括性能表现和用户体验。我们可以加入一些性能埋点,比如页面渲染时间、接口响应速度等,做基本的监控。
比如:
it('should load home page within 2 seconds', async () => {
const startTime = Date.now();
await HomePage.open();
const duration = Date.now() - startTime;
expect(duration).toBeLessThanOrEqual(2000); // 2s 内完成加载
});

5. 上架前务必实机测试
不管自动化多完善,在上传到各大应用市场之前,还是一定要进行真机验证。尤其是 iOS,苹果对自动登录、截图录制等敏感行为审查非常严格,很容易被打回。
结语:测试自动化是一场马拉松
回头来看,从最初的“人肉点点点”到如今自动化流水线能自己跑完整个测试流程,真的不容易。
但值得庆幸的是,这一路我们没有放弃,也没有盲目跟风,而是结合自身产品的实际情况,踏踏实实地走出了一条适合自己的自动化之路。
希望这篇分享能帮你在移动应用测试自动化的路上少走些弯路。如果你也在做相关的事情,欢迎留言交流,一起成长!

评论 0