测试与代码质量

单元测试、集成测试和 UI 测试的区别:一次讲清测试金字塔

学习开发时,每个人都会遇到一道墙。

4 分钟阅读
单元测试、集成测试和 UI 测试的区别:一次讲清测试金字塔 封面图

学习开发时,每个人都会遇到一道墙。

“单元测试、集成测试、UI 测试……到底有什么区别?”

先说结论。

三种测试的验证范围不同。单元测试检查一个函数,集成测试检查模块之间的连接,UI 测试则验证完整的用户界面流程。

告诉我们如何组合这三种测试的指导原则,就是“测试金字塔”。今天结合我的经验,简单讲清这个概念。


先看核心总结

为了方便忙碌的读者,先总结如下。

  1. 单元测试:只验证一个函数或类。快速、大量编写。
  2. 集成测试:验证多个模块连接后能否正常运行。数量适中。
  3. UI 测试:像点击真实界面一样验证完整流程。慢、少量编写。
  4. 测试金字塔:底部的单元测试要宽,顶部的 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. 测试需要一开始就全部写好吗?

不需要。我建议先从核心逻辑的单元测试开始。我见过很多人试图从一开始就建立完美的金字塔,最后把自己累垮。

底部要宽,顶部要窄。记住这个形状就够了
底部要宽,顶部要窄。记住这个形状就够了

这三者的区别只看文字可能很抽象,但亲自编写代码后就能真正理解。

用今天学到的内容,先给一个小函数加上单元测试吧。积累这种感觉后,其他部分自然会跟上。加油!