Squish、Qt Test 与 DOM 树:三者的定位与关系
这三个概念经常在 Qt 岗位的面试里被连着问,但它们并不在同一个层面上:Qt Test 是写在代码里的单元测试框架,Squish 是从外部驱动界面的自动化测试工具,DOM 树则是一种文档结构模型。放在一起讲的价值在于,它们刚好串成了「测试的两个层次」和「界面的一种通用抽象」。
一、一句话定位
- Qt Test:Qt 自带的单元测试框架,和被测代码编译在同一个进程里,开发者用 C++ 写,验证的是类和函数的逻辑
- Squish:Qt 公司的商业 GUI(Graphical User Interface,图形用户界面)自动化测试工具,作为独立进程 hook 被测程序 AUT(Application Under Test,被测应用),测试工程师用脚本写,验证的是完整业务流程
- DOM 树(Document Object Model,文档对象模型):把标记文档解析成节点树的模型,Web 端是浏览器渲染的基础,Qt 端对应
QDomDocument;同时也是 Squish 定位 Web 元素的依据
二、为什么需要两层测试
先看一个具体场景:地址簿程序里,点「新建」弹出对话框,填姓名后确定,列表里多一条记录。
Qt Test 能覆盖的是其中的数据层——ContactModel::addContact() 调完以后 rowCount() 是否 +1、dataChanged 信号有没有发出来。它跑得快(毫秒级)、能进 CI(Continuous Integration,持续集成)、失败时能直接定位到哪个函数,但它管不了「按钮到底连没连上这个槽」「对话框是不是真的弹出来了」。
Squish 覆盖的正是这一段:真的启动程序、真的点按钮、真的敲键盘,然后断言界面上的表格多了一行。代价是慢(秒级到分钟级)、需要图形环境、失败原因可能是界面改版而非逻辑出错。
结论很直接:两者不是替代关系,而是金字塔的两层。大量的 Qt Test 打底保证逻辑正确,少量的 Squish 用例守住关键业务路径不被改坏。
三、核心差异对照
| 维度 | Qt Test | Squish |
|---|---|---|
| 测试层次 | 单元 / 组件 | 端到端 / 系统 |
| 进程关系 | 同进程,链接被测代码 | 跨进程,注入 hook 驱动 AUT |
| 编写语言 | C++ | Python / JS / Perl / Ruby / Tcl |
| 典型执行者 | 开发者 | 测试工程师 |
| 授权 | 随 Qt 附带,免费 | 商业授权,单独付费 |
| 单个用例耗时 | 毫秒级 | 秒级到分钟级 |
| 对界面改动的敏感度 | 不敏感(不碰界面) | 敏感,靠 Object Map(对象映射表)缓解 |
| 能否发现「信号没连上」 | 不能 | 能 |
四、同一件事的两种写法
把上面那个地址簿场景写成代码,对比最直观。Qt Test 版本:
void TestContactModel::addContact() {
ContactModel model;
QSignalSpy spy(&model, &ContactModel::rowsInserted);
model.addContact({"Jifang", "guo@example.com"});
QCOMPARE(model.rowCount(), 1);
QCOMPARE(spy.count(), 1); // 确认信号发了
QCOMPARE(model.index(0, 0).data().toString(), QString("Jifang"));
}
Squish 版本:
def main():
startApplication("addressbook")
clickButton(waitForObject(":Address Book.New_QToolButton"))
type(waitForObject(":Forename:_QLineEdit"), "Jifang")
clickButton(waitForObject(":Address Book - Unnamed.OK_QPushButton"))
table = waitForObject(":Address Book.contacts_QTableWidget")
test.compare(table.rowCount, 1)
注意 Squish 那段里 table.rowCount 是直接读 Qt 对象的属性——它靠元对象系统拿到了真实的 QTableWidget,不是在截图上数像素。这是 Squish 和图像比对类工具(SikuliX 之类)的根本区别,也是它能容忍换皮肤、改分辨率、挪控件位置的原因。
五、DOM 树在这里扮演什么角色
DOM 树本身是 W3C 的文档模型标准,把标记文本解析成节点树:
Document
└── html (Element)
├── head
│ └── title
│ └── "标题" (Text)
└── body
└── div id="app"
它和前两者的连接点有两处。
第一处,Squish 测 Web 应用时,定位元素走的就是 DOM。测 Qt 应用写 {type='QPushButton' text='OK'},测 Web 应用写 {tagName='BUTTON' innerText='提交'}——同一套 API,底下换了棵树。
第二处,也是更有意思的一点:DOM 树、Qt 的 QObject parent-child 树、QML 的 Item 树,本质是同一个设计思路。都是层级容器树,查找方式一一对应:
| 能力 | Web DOM | Qt |
|---|---|---|
| 按标识查找 | getElementById | findChild<T*>(name) |
| 按条件批量查找 | querySelectorAll | findChildren<T*>() |
| 样式选择器 | CSS | QSS |
| 访问父节点 | parentNode | parent() |
| 路径表达式 | XPath | Squish 对象名表达式 |
理解了这层同构关系,学任意一棵树的遍历套路,另外两棵就是换个 API 名字的事。
六、怎么选
实践上的经验:
- 业务逻辑、数据处理、算法——一律 Qt Test,进 CI 每次提交都跑
- Model/View 的数据正确性——Qt Test 覆盖 model,view 交给 Squish 抽查
- 关键业务流程(登录、下单、保存导出)——Squish 用例,每日或每次发版前跑
- 纯界面细节(间距、配色)——两者都不合适,走人工验收或视觉回归工具
一个常见的误区是想用 Squish 覆盖所有场景。它的用例维护成本远高于单元测试,界面一改就得跟着调 Object Map,铺太多会让团队被测试拖住。
术语表
| 简称 | 英文全称 | 中文与说明 |
|---|---|---|
| DOM | Document Object Model | 文档对象模型。把标记文档解析成节点树的 W3C 标准 |
| AUT | Application Under Test | 被测应用。Squish 术语,指被 hook 并驱动的目标程序 |
| GUI | Graphical User Interface | 图形用户界面 |
| CI | Continuous Integration | 持续集成。每次提交自动构建并跑测试 |
| — | Object Map | 对象映射表。Squish 中符号名到控件属性组合的映射文件 |
| — | Meta-Object System | 元对象系统。Qt 的运行时反射机制,Squish 靠它读取控件属性 |
| — | Model/View | 模型/视图。Qt 的数据与展示分离架构 |
| W3C | World Wide Web Consortium | 万维网联盟。DOM 标准的制定组织 |
| XPath | XML Path Language | XML 路径语言。用路径表达式定位树节点 |
| CSS | Cascading Style Sheets | 层叠样式表 |
| QSS | Qt Style Sheets | Qt 样式表。CSS 的子集 |
| API | Application Programming Interface | 应用程序编程接口 |
延伸阅读
这三块各自还有不少细节,分别整理了单独的文章:
- Squish 深入:架构、对象识别与实战要点
- Qt Test 深入:从 QCOMPARE 到数据驱动测试
- DOM 树深入:从浏览器渲染到 QDomDocument

浙公网安备 33010602011771号