测试与代码质量

单元测试、集成测试和 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. 测试需要一开始就全部写好吗?

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

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

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

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