78、UI自动化测试:Espresso、UI Automator、录制回放、持续集成

说实话,UI自动化测试这个话题,很多Android开发者是又爱又恨。爱的是它能帮我们省下大量回归测试的时间,恨的是——写起来麻烦,跑起来不稳定,维护起来想骂人。

我最早接触UI自动化是在一个金融App项目里。那时候团队只有两个人写测试,每次发版前要手动点几百个页面,点得手指发麻。后来我实在受不了了,决定把Espresso和UI Automator捡起来好好搞一搞。嗯,这一搞就是三年。

今天我就把这三年的经验浓缩一下,跟你聊聊Android UI自动化测试的四个核心方向:EspressoUI Automator录制回放持续集成

核心观点:UI自动化测试不是银弹,但它能帮你把「手动点100遍」变成「一键跑100遍」。关键在于选对工具、写好用例、搭好流水线。

Android UI自动化测试知识体系 UI自动化测试 Espresso UI Automator 录制回放 持续集成 onView IdlingResource UiDevice 跨App测试 Espresso Recorder 自定义录制 Jenkins/GitLab CI Firebase Test Lab 目标:稳定、快速、可维护的UI自动化测试体系

一、Espresso:单App内的UI测试利器

Espresso是Google官方推出的UI测试框架,专门用来测试单个App内部的UI交互。它的核心思想就一句话:在真实设备上模拟用户操作,然后验证UI状态

我个人习惯把Espresso的用法总结成三步:

  1. 找控件:用 onView() 配合 Matcher 找到目标 View
  2. 做操作:用 perform() 执行点击、输入等动作
  3. 验结果:用 check() 验证UI是否符合预期

举个例子,测试一个登录按钮的点击效果:

@Test
public void testLoginButton() {
    // 找到登录按钮
    onView(withId(R.id.btn_login))
        .perform(click());  // 点击它
    
    // 验证是否跳转到主页
    onView(withId(R.id.tv_welcome))
        .check(matches(isDisplayed()));
}

看着简单吧?但实际项目中坑不少。我遇到过最典型的问题就是异步操作。比如网络请求还没回来,Espresso就已经开始检查UI了,结果测试必挂。

我的经验:遇到异步问题,别急着加 Thread.sleep()。用 IdlingResource 才是正解。它能让Espresso乖乖等到异步任务完成再继续执行。我在一个视频App项目里,就是用IdlingResource解决了90%的测试不稳定问题。

IdlingResource的用法大致是这样:

// 注册空闲资源
IdlingRegistry.getInstance()
    .register(myIdlingResource);

// 执行测试
onView(withId(R.id.btn_load))
    .perform(click());

// 测试结束后注销
IdlingRegistry.getInstance()
    .unregister(myIdlingResource);

二、UI Automator:跨App测试的王者

Espresso只能测你自己的App,但UI Automator不一样。它能跨App操作,比如打开系统设置、操作第三方App、甚至模拟系统级事件。

说白了,UI Automator就是你的「系统级手指」。我在做支付SDK测试时,经常用它来模拟拉起支付宝或微信支付,然后验证支付结果。

核心API就两个:

  • UiDevice:代表设备本身,可以按电源键、旋转屏幕、按返回键
  • UiObject:代表界面上的控件,通过文本、描述、类名等方式定位

看个例子,打开系统设置里的WiFi开关:

// 获取设备实例
UiDevice device = UiDevice.getInstance(
    InstrumentationRegistry.getInstrumentation());

// 按Home键回到桌面
device.pressHome();

// 打开设置App
device.executeShellCommand(
    "am start -a android.settings.SETTINGS");

// 找到WiFi菜单项并点击
UiObject wifiSetting = device.findObject(
    new UiSelector().text("Wi-Fi"));
wifiSetting.clickAndWaitForNewWindow();

注意:UI Automator的定位方式依赖UI文本和描述,如果App做了国际化,测试用例也得跟着适配。我曾经因为App从中文切换到英文,导致一整套UI Automator用例全部失效……嗯,从那以后我养成了用资源ID定位的习惯。

三、录制回放:快速生成测试用例

写UI测试用例最烦的是什么?是手写那些 onView(withId(...)) 的样板代码。录制回放功能就是为了解决这个问题。

Android Studio自带的 Espresso Test Recorder 可以帮你录制操作,然后自动生成测试代码。你只需要在模拟器上点几下,代码就出来了。

操作步骤很简单:

  1. 在Android Studio中点击 Run > Record Espresso Test
  2. 在模拟器上执行你的操作(点击、输入、滑动等)
  3. 录制完成后,添加断言(比如检查某个文本是否显示)
  4. 保存,代码自动生成

但说实话,录制生成的代码只能当草稿用。我试过几次,生成的代码里经常有多余的等待时间、不准确的定位方式。我的建议是:

  • 用录制快速搭建测试骨架
  • 然后手动优化定位方式和断言逻辑
  • 把公共操作(比如登录)提取成独立方法

一个小技巧:如果你不想用官方的Recorder,也可以自己写一个简单的录制工具。我在一个项目中用 AccessibilityService 监听用户操作,然后自动生成测试脚本。虽然有点重,但对于复杂交互场景特别管用。

四、持续集成:让自动化测试跑起来

UI自动化测试写好了,不跑起来就是白写。持续集成(CI)就是让测试自动运行的流水线。

我常用的CI方案有两种:

方案 适用场景 优点 缺点
Jenkins + 本地设备 小团队、内部测试 免费、可控 需要维护设备集群
Firebase Test Lab 中大型团队、云端测试 多设备、多版本、自动截图 按量付费
GitLab CI + 模拟器 轻量级、快速反馈 集成方便、成本低 模拟器不如真机准

我个人比较推荐 GitLab CI + Firebase Test Lab 的组合。GitLab CI负责触发构建和上传APK,Firebase Test Lab负责在云端跑测试。这样你本地不需要维护任何设备。

一个简单的 .gitlab-ci.yml 配置片段:

ui-test:
  stage: test
  script:
    - ./gradlew assembleDebug
    - ./gradlew assembleDebugAndroidTest
    - gcloud firebase test android run
        --app app/build/outputs/apk/debug/app-debug.apk
        --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
        --device model=Pixel2,version=28,locale=zh_CN
  only:
    - develop

避坑指南:我曾经在CI上吃过一个大亏——测试用例在本地跑全绿,上了CI就各种红。后来发现是CI上的设备语言是英文,而我的测试用例里写死了中文文本。解决方案很简单:要么在测试前设置设备语言,要么用资源ID代替文本匹配。

五、实战建议:搭建你的UI自动化测试体系

说了这么多,最后给你几个落地建议:

  1. 先核心后边缘:别一上来就想覆盖所有页面。先把登录、注册、支付这些核心流程测好,再慢慢扩展。
  2. 用例要稳定:一个不稳定的测试用例比没有更糟糕。它会让团队对自动化失去信心。我见过太多团队因为用例频繁误报,最后把CI上的UI测试给禁用了。
  3. 截图留证据:测试失败时自动截图,方便排查问题。Firebase Test Lab自带这个功能,Jenkins的话需要自己写插件。
  4. 分层设计:把公共操作(登录、导航)封装成Page Object模式,避免每个用例都重复写一遍。

最后说一句:UI自动化测试不是一蹴而就的事。它需要持续投入、持续优化。但只要你坚持下来,你会发现——发版前的焦虑少了,周末加班的次数少了,产品的质量也稳了。这笔账,怎么算都划算。


公众号:蓝海资料掘金营,微信deep3321