学习开发时,每个人都会遇到一道墙。
“单元测试、集成测试、UI 测试……到底有什么区别?”
先说结论。
三种测试的验证范围不同。单元测试检查一个函数,集成测试检查模块之间的连接,UI 测试则验证完整的用户界面流程。
告诉我们如何组合这三种测试的指导原则,就是“测试金字塔”。今天结合我的经验,简单讲清这个概念。
先看核心总结
为了方便忙碌的读者,先总结如下。
- 单元测试:只验证一个函数或类。快速、大量编写。
- 集成测试:验证多个模块连接后能否正常运行。数量适中。
- UI 测试:像点击真实界面一样验证完整流程。慢、少量编写。
- 测试金字塔:底部的单元测试要宽,顶部的 UI 测试要窄。
记住这四行,就理解一半了。接下来逐一拆解。
单元测试、集成测试、UI 测试有什么区别?
这是最容易混淆的部分,我们先从比喻开始。
想象一下你正在制造汽车。
单元测试就像检查一颗螺丝或螺栓是否符合规格,是非常小的单位。
集成测试则是检查发动机和变速箱连接后能否顺畅啮合运转。
UI 测试就像坐进完成的汽车,启动引擎并实际驾驶。
用表格比较,可以整理成这样。
| 分类 | 验证范围 | 速度 | 编写数量 |
|---|---|---|---|
| 单元测试 | 一个函数或类 | 非常快(ms) | 多 |
| 集成测试 | 模块间集成 | 一般(秒级) | 中等 |
| UI 测试 | 完整用户流程 | 慢(几十秒) | 少 |
核心区别在于覆盖范围有多大。
范围越广,测试越接近真实使用环境,但也会越慢,管理起来越麻烦。
单元测试就是这种感觉
光说不容易理解,我们来看一个简短示例。
下面是一个只验证两个数字相加函数的单元测试。
// 只验证加法函数的单元测试
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
@Test func 加法_2_相加_3是_5() {
#expect(add(2, 3) == 5)
}
不需要外部数据库,也不需要界面,只要单独取出一个函数进行检查。
所以执行会在眨眼间完成。即使运行几百个,也只需要几秒。
我最常使用单元测试,就是因为它足够快。修改代码后可以立即验证。
什么是测试金字塔?
现在进入今天的主角:测试金字塔。
这是 Mike Cohn 在 2009 年出版的《Succeeding with Agile》中提出的概念,主张把测试堆成三角形。
- 最底层(宽):单元测试 — 最多
- 中间:集成测试 — 适量
- 最顶层(窄):UI 测试 — 最少
为什么是这种形状?
因为越往下,测试越快、越便宜。运行几千个单元测试也没有负担。
相反,UI 测试速度慢,界面稍微变化就很容易失败,维护成本也很高。
多写便宜、快速的测试,少写昂贵、缓慢的测试。这就是测试金字塔的全部。
如果把金字塔倒过来堆,就叫作“冰淇淋甜筒”反模式。这种状态下 UI 测试很多,却没有单元测试,很容易被缓慢且脆弱的测试拖住。
那么比例该怎么确定?
这是很多人都想知道的问题。
常被引用的标准大约是单元 70%:集成 20%:UI 10%。
不过这不是绝对规则,应根据项目性质灵活调整。
例如,对于后端 API 服务器,提高集成测试的比例更实用。Google 等组织也强调保持“金字塔形状”,而不是追求精确数字。
再分享一个我亲身经历后的建议。
不要执着于数字。重要的是养成检查“是否过度依赖缓慢且容易失败的测试”的习惯。
常见问题(Q&A)
Q. E2E 测试和 UI 测试是一样的吗?
两者几乎可以互换使用,但严格来说并不相同。E2E(End-to-End)指从用户角度验证完整流程,而 UI 测试则专注于操作界面。在实际工作中经常混用。
Q. 只要把集成测试写好,就可以不做单元测试吗?
不建议。集成测试很难准确定位问题发生在哪里。有了单元测试,才能快速缩小原因范围。
Q. 测试需要一开始就全部写好吗?
不需要。我建议先从核心逻辑的单元测试开始。我见过很多人试图从一开始就建立完美的金字塔,最后把自己累垮。
这三者的区别只看文字可能很抽象,但亲自编写代码后就能真正理解。
用今天学到的内容,先给一个小函数加上单元测试吧。积累这种感觉后,其他部分自然会跟上。加油!

