当前位置:首页>鸿蒙APP>鸿蒙原生软件开发之UI自动化测试

鸿蒙原生软件开发之UI自动化测试

  • 2026-10-11 05:26:12
鸿蒙原生软件开发之UI自动化测试

我准备连载一个原生鸿蒙开发系列,希望通过系列文章带你一同了解我是如何从0开发一款鸿蒙原生软件的,欢迎订阅关注。

一次持续 12 小时的原生鸿蒙开发调试经历

我竟然发现了原生鸿蒙的一个底层 bug

Vibe Coding原生鸿蒙软件需要鸿蒙专家上阵

鸿蒙原生软件界面布局中的那些坑

TUTORIAL · 鸿蒙实战2026.08

界面回归靠肉眼截图?

别再用肉眼审 UI

让屏幕自己说话

鸿蒙 UI 自动化测试基建 · 一次投入,长期复利

刘顾问客厅

鸿蒙测试基建

📦 7 PARTS + CONCLUSION

👉 滑动

PART 01

为什么非搭不可

三个痛点

PART 02

搭起来长啥样

六步路径

PART 03

难在哪

四个坑

PART 04

难搭却值得

钻石与同源

PART 05

落地骨架

代码与对比

PART 06

五条经验

给开发者

PART 07

易错清单

自查三步

PART ///

写在最后

收束

前些日子,memos 鸿蒙版的一个下午,我盯着真机屏幕,手指划了划,截了张图,眯着眼跟自己说:「这回……应该好了吧?」

这话,我自己都不信。

界面回归靠「真机手动点 + 截图肉眼比」,主观、慢、还不可重复——同一套操作做两遍,结果「看起来差不多」,可「差不多」在 bug 面前,就是差很多。那天我终于下决心:把这活儿,一次性自动化了。

结果出乎意料:基建当天就回本。此后每一个「界面不刷新 / 布局错位」的问题,我不再靠「我觉得好了」,而是让屏幕用 uitest dumpLayout 把真实文本和坐标甩出来——一锤定音。

这套东西,值得每个高频迭代的鸿蒙团队在项目早期就位。下面把来龙去脉、踩过的坑、落地的骨架,一次讲透。

让屏幕用 dumpLayout 把真实文本和坐标甩出来 —— 一锤定音

01

PART

为什么非搭不可:三个扎心的痛点

PAIN POINTS · 测试基建

先说清一件事:这篇文章讲的不是「修某个 bug」,而是一次测试基建的投入。

此前我们回归靠「真机手动操作 + 截图肉眼对比」,三条原罪,条条要命:

1

主观且不可重复:手动操作加截图对比,同一个操作两次结果「看起来差不多」,可你没法向同事证明「真的好了」。

2

成本随功能线性膨胀:每天十几个 commit 的节奏下,全量回归越来越贵,最后只能保主路径,边缘场景直接漏测。

3

单测根本够不着界面:src/test 里的单元测试测的是纯逻辑——markdown 解析、filter 构造;可界面显示、滚动位置、键盘行为,必须真机才验得了。

任正非常说「板凳要坐十年冷」。测试基建也是一样——别等债堆到还不起才动手,越早把板凳坐热,复利来得越早。

典型场景
我们要验证什么
没有基建时的代价
保存后列表刷新
新 memo 是否出现在列表
手动操作 + 截图对比(慢)
布局错位
元素坐标是否正确
肉眼看截图(主观)
滚动可达性
内容能否滚到顶部
手动滑 + 观察(不可重复)
回归基线
每个 commit 后快速验证
全手动(贵到直接放弃)

02

PART

搭起来长啥样:一套命令跑完全部 UI 用例

BLUEPRINT · 搭建路径

我们这次的实战样本,是 memos 鸿蒙版 2026-08-09 那一仗。搭完之后是这个形态:

形态

建了 ohosTest 测试工程:UiTestEnv(环境初始化)、UiTestUtil(公共操作:点击 / 滑动 / 等待)、五个页面对象、六个测试套件(含资源页套件)。

入口

聚合入口 List.test.ets,一条命令跑完全部 UI 用例。

验证

当天下午,每个界面修复都用 hdc shell uitest dumpLayout 读屏幕真实文本加坐标验证——从「我看图觉得好了」变成「屏幕文本证明好了」。

怎么搭?把完整路径摊开,六步走

1

建立 ohosTest 测试模块(entry/src/ohosTest/)

2

引入 @kit.TestKit:Driver.create() 获取 UI 驱动

3

抽象 UiTestEnv(启动 / 进入指定页面 / 清理)

4

抽象 UiTestUtil(公共操作:clickByText / swipe / 等待元素)

5

按页面建页面对象(MemoListPage / MemoDetailPage / SearchPage / ResourcePage…)

6

每个页面对象暴露「断言方法」(expect 某文本可见、某坐标存在)

地基一旦铺好,之后每写一条用例,都是廉价的增量——这才是关键。

03

PART

难在哪:四个坑,我替你踩了一遍

PITFALLS · 四个难点

CASE 01

难点一:TestKit API 不熟

现象:Driver.create() 的时机、ON.text() 的匹配方式、等待机制都不明确,示例又少。成因:TestKit 相对小众,文档和实战用例都稀缺,ON.text() 到底要精确匹配还是包含匹配,得自己试出来。

CASE 02

难点二:页面对象抽象粒度

现象:用例和页面细节耦合,页面结构一改,用例全崩。成因:没做「页面对象」分层,选择器散落得到处都是。

CASE 03

难点三:注入与真机手感有差异

现象:uiInput 注入的点击 / 滑动,和真机手指行为并不完全一致——部分手势 API 对注入不响应。成因:注入属于「合成事件」,底层手势识别路径未必认这个,所以选型时就要想清楚「被测能力是否可注入」。

CASE 04

难点四:等待与稳定性

现象:用例偶发失败,往往是元素还没渲染完就断言了。成因:缺的是等待机制——固定 sleep 也好、条件等待也好,总得有个说法。

04

PART

为什么难搭却值得:地基是钻石,用例是砖头

WHY IT PAYS · 复利本质

这一节是全文的魂,请各位朋友放慢看。难搭的原因,一句话:成本全压在地基上,不在用例上。

...text

TestKit 文档少、示例少 → 摸 API 语义耗时(Driver.create / ON.text / 等待)

页面对象抽象需要「先想清楚分层」 → 前期投入高于写一条用例

注入与真机差异 → 选型时就要考虑「被测能力是否可注入」

根本难点:UI 自动化的成本在「地基」——环境、抽象、公共操作;地基一次搭好,用例是廉价的。这就好比盖楼,地基是钻石级的投入,往上砌的砖头,一块块都不贵。

那为什么还值得?因为测试工具和排障工具,是同源的

...text

UI 测试工具 = 排查工具:

  pytest 用 dumpLayout 断言屏幕文本 → 排查时也用 dumpLayout 看屏幕真相

  pytest 用 uiInput 注入操作 → 排查时也用 uiInput 复现

一次基建,测试与排障双复用 —— 这才是「复利」的真正来源

芒格那句老话放到这儿特别应景:「复利的第一条规则,是非必要别打断它。」你搭一次基建,既能跑回归,又能查问题,两件事共用一套实证手段——当天下午修复验证效率的飙升,根子就在这儿。

05

PART

落地骨架:代码、API、与一张对比表

BLUEPRINT · 落地代码

公共操作 + 页面对象

...ts

// UiTestUtil:公共操作(点击文本 / 滑动 / 等待元素)

export class UiTestUtil {

  static async clickByText(driver: Driver, text: string): Promise<void> {

    await driver.findComponent(ON.text(text)).click();

  }

  static async waitForText(driver: Driver, text: string, timeout = 3000): Promise<void> {

    // 条件等待:元素出现才继续,避免固定 sleep

  }

}

// 页面对象:一个页面一个类,暴露断言方法

export class MemoDetailPage {

  static async expectTitle(driver: Driver, title: string): Promise<void> {

    await driver.findComponent(ON.text(title)).assertExist();

  }

}

// 套件聚合:List.test.ets 一条命令跑全部

// (5 个页面对象 + 6 个套件,全部注册进 List.test.ets)

关键 API 结论(本 SDK API 24 实测)

API
结论
Driver.create()
每个用例开头创建 UI 驱动
ON.text()
按文本匹配组件,配合 .assertExist()
uitest dumpLayout
命令行读屏幕真实文本 + 坐标(排障 / 断言双用)
uitest uiInput click/swipe
注入操作(部分手势 API 对注入不响应,onTouch 方案响应良好)

稳定性:条件等待替代固定 sleep

UI 用例八成的偶发失败,都来自「等得不够」。

...ts

// 反模式:await sleep(2000)(动画 / 加载慢时偶发失败)

// 正模式:条件等待(轮询直到元素出现或超时)

await driver.findComponent(ON.text('目标文本'))

  .waitUntil(async (comp) => await comp.isExist(), 3000);

方案对比与选择理由

维度
手工回归(教训)
UI 自动化(采用)
速度
慢(每项人工操作)
快(一条命令全量)
可重复
差(主观)
好(确定性断言)
覆盖
主路径为主
可全量(含边缘)
排障复用
无
同工具(dumpLayout / uiInput)
前期投入
无
一次基建(环境 + 抽象)

选择理由:前期投入(半天级)换「测试 + 排障」双复用加回归自动化——对高频迭代项目,是明确的净收益。重要的事说三遍:同源、同源、还是同源。

06

PART

给鸿蒙开发者的五条经验

LESSONS · 五条经验

1

测试基建「地基重、用例轻」:环境、页面对象、公共操作一次性搭好,之后每条用例都是廉价增量——别让每条用例自己摸 API。

2

测试工具与排障工具同源:dumpLayout 既能断言屏幕文本,也能排查「界面到底显示了什么」;uiInput 既能自动化操作,也能复现用户路径。搭基建时优先选「排障也用得上」的工具能力。

3

条件等待大于固定 sleep:UI 用例的偶发失败八成来自等待不足——用「轮询直到元素出现」替代「睡两秒」。

4

注入兼容性影响选型:被测能力(尤其手势)要确认对 uiInput 注入是否响应——onTouch方案对注入响应良好、PanGesture 可能失灵,选型时就把可测试性算进去。

5

断言用「屏幕真实文本」而非「肉眼截图」:dumpLayout 的文本加坐标是确定性的;截图对比主观且没法自动化。

07

PART

易错点清单与自查三步

CHECKLIST · 避坑

怕你踩坑,附一份对照自查表

#
陷阱
症状
自查点
1
固定 sleep 等待
用例偶发失败
是否条件等待
2
选择器散落用例里
页面改版全崩
是否页面对象分层
3
Driver 未每用例创建
驱动失效
每个用例开头 Driver.create()
4
手势用例对注入不响应
用例恒失败
是否确认被测能力可注入
5
断言用截图肉眼
不可自动化
是否用 dumpLayout 文本断言
6
套件未聚合
一条条跑
是否聚合到 List.test.ets

自查三步走

...text

① 用例不稳定? → 等待改条件等待(轮询元素出现)

② 页面改版就崩? → 选择器收进页面对象(页面对象分层)

③ 断言主观? → 改 dumpLayout 文本断言(确定性)

已验证的执行命令,建议直接收藏

...bash

# UI 测试编译(ohosTest 模块)

./hvigorw assembleHap --mode module -p module=entry@ohosTest --no-daemon

# 运行测试套件(src/test 单测)

./hvigorw test --mode module -p product=default --no-daemon

# 排障 / 断言双用:读屏幕真实文本

hdc shell uitest dumpLayout

# 注入操作

hdc shell uitest uiInput click 550 600

///

LAST

写在最后

WRAP-UP · 收束

UI 测试基建的本质是「前期投入 versus 长期复利」的取舍;而它在我们项目里的回报,被证明是「复利」而非「投入」:一套环境与工具,同时服务回归自动化与问题定位,当天下午的每一个界面修复都因此提速。

核心经验三条,我把它刻在工位上:地基一次搭好(环境 + 页面对象 + 公共操作)、工具选排障同源的、等待用条件式。对任何高频迭代的鸿蒙项目,这套基建都值得在项目早期就位——别等债堆高了,才想起补地基。

我是 刘顾问,关注我,我们一同进步。

如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连,我们下篇见

赞
在看
收藏

THANKS FOR READING

最新文章

随机文章