A hand-drawn doodle illustration on pure white pap

前言

你有没有试过改了一行代码,结果三个页面炸了?我上个月就干过这事——改了个按钮颜色,把整个订单流程搞崩了。手动测了两天才把问题找全,人都麻了。

从那以后我就在想:要是有一套自动化测试能替我点点点,该多好。

折腾了一周 UiTest 框架,终于跑通了第一个 UI 自动化测试用例。今天把整个过程分享出来,帮你少踩坑。
HarmonyOS7 提供了官方的 UI 自动化测试框架 UiTest,专门用来对 ArkUI 页面做自动化验证。说实话,之前我一直觉得写测试是浪费时间,直到那次改一行炸三页的事故,才真正意识到——没有测试的代码,就像没有刹车的车,早晚出事。

为什么要写 UI 测试

先聊聊为什么需要 UI 测试,而不是只靠单元测试。

单元测试管的是逻辑,但用户看到的、操作的是 UI。按钮能不能点?页面跳转对不对?列表滑到底部会不会崩?这些问题单元测试覆盖不了。

我之前的项目有个搜索页,单元测试全过,结果真机上输入法一弹出来就把搜索按钮遮住了。这种问题,只有 UI 测试能发现。

UI 自动化测试能帮我们做到:

  • 回归保障:改代码不怕旧功能炸
  • 持续集成:每次提交自动跑测试
  • 减少手动测试:把重复的点点点交给机器

UiTest 框架概览

UiTest 是 HarmonyOS7 官方的 UI 自动化测试框架,核心能力包括:

能力 说明

A hand-drawn doodle illustration on pure white pap

| 组件查找 | 通过 id、text、type 等属性定位 UI 组件 |
| 模拟操作 | 点击、长按、滑动、输入文本等 |
| 断言验证 | 检查组件属性、页面状态是否符合预期 |
| 测试报告 | 自动生成测试结果报告 |

它的 API 风格跟 Android 的 UiAutomator 有点像,但更简洁。如果你写过 Android UI 测试,上手会很快。

环境搭建

写 UI 测试之前,得先把环境搭好。步骤不复杂,但有几个坑要注意。

1. 在模块的 oh-package.json5 中添加依赖:

{
  "devDependencies": {
    "@ohos/hypium": "1.0.6"
  }
}

2. 在测试目录下创建测试文件:

测试文件放在 entry/src/ohosTest/ets/ 目录下,别放错位置了。ohosTest 是专门放仪器测试的目录,跟 test 目录不一样。

3. 配置测试运行器:

module.json5 里确认有测试配置:

{
  "abilities": [
    {
      "name": "EntryAbility",
      "srcEntry": "./ets/EntryAbility.ets"
    }
  ]
}

A hand-drawn doodle illustration on pure white pap

划重点:测试文件必须在 ohosTest 目录下,否则框架识别不了。

查找组件

UiTest 的核心思路就两步:找到组件,操作组件。 找组件是第一步,也是最关键的一步。

按 id 查找:

import { Driver, ON } from '@ohos.UiTest';

const driver = Driver.create();
const button = await driver.findComponent(ON.id('loginButton'));

这段代码通过组件的 id 来查找。ON.id() 是最精确的查找方式,前提是你给组件设了 id

按文本查找:

const textComp = await driver.findComponent(ON.text('登录'));

当组件没有设 id 时,可以通过显示文本来查找。不过这种方式有个隐患——文本改了,测试就挂了。 所以还是建议给关键组件加 id。

按类型查找:

const listComp = await driver.findComponent(ON.type('List'));

按组件类型查找,适合需要操作某个类型的容器组件时使用。

敲黑板:查找组件返回的是 Promise,必须用 await。忘了加 await 是新手最常见的坑。

模拟操作

找到组件后,就可以模拟用户操作了。

点击操作:

const loginBtn = await driver.findComponent(ON.id('loginButton'));
await loginBtn.click();

click() 模拟单击。如果你需要长按,用 longClick()

滑动操作:

await driver.swipe(200, 500, 200, 100, 500);
// 参数:起点x, 起点y, 终点x, 终点y, 滑动速度(ms)

swipe() 是屏幕级别的滑动,适合滚动页面、下拉刷新等场景。

输入文本:

const inputComp = await driver.findComponent(ON.id('searchInput'));
await inputComp.inputText('HarmonyOS7');

往输入框里填文字。注意这个操作会先清空原有内容,再输入新内容。

组合操作示例——模拟登录流程:

async function testLogin() {
  const driver = Driver.create();

  // 输入用户名
  const usernameInput = await driver.findComponent(ON.id('username'));
  await usernameInput.inputText('testuser');

  // 输入密码
  const passwordInput = await driver.findComponent(ON.id('password'));
  await passwordInput.inputText('123456');

  // 点击登录按钮
  const loginBtn = await driver.findComponent(ON.id('loginButton'));
  await loginBtn.click();

  // 等待页面响应
  await driver.delayMs(1000);
}

这段代码模拟了完整的登录操作。delayMs() 用来等待页面跳转,时间根据实际情况调整。

断言验证

操作完了,得验证结果对不对。这才是测试的核心——光操作不验证,那叫演示,不叫测试。

验证组件是否可见:

import { assert } from '@ohos/hypium';

const homeTitle = await driver.findComponent(ON.text('首页'));
assert.assertTrue(homeTitle !== null, '登录后应跳转到首页');

这段代码检查登录后是否出现了"首页"文字。如果组件找不到,说明跳转没成功。

验证组件属性:

const statusText = await driver.findComponent(ON.id('orderStatus'));
const text = await statusText.getText();
assert.assertEqual(text, '已完成', '订单状态应为已完成');

先拿到组件,再读取它的文本内容,跟预期值对比。assertEqual 会在值不匹配时抛出异常,测试标记为失败。

验证组件数量:

const listItems = await driver.findComponents(ON.type('ListItem'));
assert.assertEqual(listItems.length, 10, '列表应显示10条数据');

注意 findComponents 带 s,返回所有匹配的组件数组。适合验证列表数据是否正确加载。

血泪教训:断言一定要写清楚错误信息,不然测试挂了你看半天不知道哪里出了问题。

测试报告

测试跑完会自动生成报告。在 DevEco Studio 里右键测试文件 → Run Tests,执行完就能看到结果。

报告里包含:

信息 说明
用例总数 总共跑了多少条测试
通过数 绿色的,没问题的
失败数 红色的,需要排查
执行时间 每条用例耗时
失败截图 断言失败时自动截图

建议把 UI 测试集成到 CI 流水线里,每次提交代码自动跑一遍。这样谁改坏了东西,第一时间就能发现。

写在最后

UiTest 框架上手不难,关键是把组件 id 规划好。我现在的习惯是:所有用户可交互的组件必须设 id,按钮用 btnXxx,输入框用 inputXxx,统一命名。这样写测试的时候查找组件就轻松了。

还有一点:别一上来就想覆盖所有场景。 先从核心流程开始——登录、支付、关键操作路径,把这些保住了,再慢慢扩展。测试覆盖率 20% 但都是关键路径,比覆盖率 80% 但全是边缘场景有用得多。

如果你还没写过 UI 自动化测试,今天就可以动手试试。先跑通一个用例,你就知道有多香了。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐