4. 测试用例分层模型:测试金字塔与反模式剖析
聊到CI的用例组织,我脑子里第一个蹦出来的就是测试金字塔。这东西在微服务、模块化架构里,其实比传统单体项目更关键。为什么呢?因为你拆得越细,层与层之间的信任就越需要靠精准的测试来维系。
说实话,我见过太多团队一上来就猛写E2E,觉得“覆盖得全才放心”。结果CI跑一次四十分钟,开发改一行代码要等半天……这种体验,谁试谁知道。今天咱们就把金字塔每一层掰开看看,顺便聊聊那些常见的坑。
4.1 单元测试:守住代码的最小逻辑单元
单元测试是金字塔的底座,也是我最看重的一层。说白了,它测的是你写的那个函数、那个方法,在给定输入下能不能按预期输出。在模块化架构里,每个模块的公共函数、工具库、状态处理逻辑,都应该被单元测试先罩住。
我个人习惯:一个函数如果超过三行逻辑,或者有分支、有异常处理,那就值得写一个单元测试。你可以用 Jest、Vitest 或 JUnit 这类框架,跑起来毫秒级,CI 里几百个单元测试几秒完事。
// 一个简单的单元测试示例(Jest)
const { calcDiscount } = require('./pricing');
test('折扣后金额不低于0', () => {
expect(calcDiscount(10, 150)).toBe(0); // 满100打几折?
expect(calcDiscount(0, 100)).toBe(100); // 无折扣
expect(calcDiscount(20, 0)).toBe(0); // 原价0
});
4.2 集成测试:确保模块间“握手”成功
单元测试搞定后,下一步就是看你的模块能不能和外界正常沟通。集成测试关注的是接口契约、数据库交互、消息队列调用、RPC 调用等等。它不需要真实环境全跑起来,但是要 mock 掉外部依赖的边界。
嗯,这里要特别强调:集成测试千万别写成 mini 版 E2E。我见过有些人把整个微服务启动,然后测一圈,那跟 E2E 有啥区别?集成测试应该聚焦在“当前模块对上游的依赖是否正确消费”,而不是“完整业务流程”。
| 测试类型 | 关注点 | 典型依赖 | 速度 |
|---|---|---|---|
| 单元测试 | 函数逻辑、边界条件 | 无(纯内存) | 毫秒级 |
| 集成测试 | 模块间接口、数据流转 | Mock Server / 嵌入式DB | 秒级 |
| E2E 测试 | 用户场景、关键链路 | 完整环境(DB、第三方) | 分钟级 |
举个例子,你在做支付模块的集成测试,只需要 mock 外部网关的返回,验证你的业务层在“支付成功/失败/超时”三种情况下,数据库状态更新是否正确。不需要真的去扣一笔钱。
4.3 E2E 测试:只保命,不保装
E2E 是金字塔最上面那层,也是最脆弱的。因为它依赖完整环境:前端、后端、中间件、第三方服务都得在线。而且一跑就是几分钟。所以我说,E2E 只用来保住核心业务路径,不是用来覆盖所有功能的。
在模块化架构下,E2E 更适合做“冒烟检测”和“回归验证”。比如用户注册登录、下单支付这条主线。其他分支逻辑,尽量下沉到集成测试去覆盖。
我个人习惯:每个微服务只配 3~5 条 E2E,CI 里所有 E2E 加在一起不超过 20 条。多了就变成了负担,开发恨它,QA 也骂它不稳定。
4.4 反模式:这些坑你最好别踩
测试金字塔本身是好东西,但很多人用着用着就跑偏了。我总结了三种常见的反模式,咱们一个一个看。
4.4.1 冰淇淋反模式
金字塔倒过来了:顶部 E2E 很大,底部的单元测试反而很少。这种情况通常出现在“测试后补”的团队,先写了 UI 自动化,后期才补单元。结果就是 CI 慢、维护成本高、定位问题难。
为什么说它反?因为你跑 E2E 失败的时候,根本不知道是哪一层出了问题。前端、后端、数据库都有可能。排查一圈下来,时间都耗光了。
- 特征:E2E 用例数量 > 集成测试 > 单元测试
- 后果:CI 时长失控,反馈延迟
- 建议:逐步将 E2E 拆解为集成 + 单元,降低金字塔顶部厚度
4.4.2 沙漏反模式
单元测试和 E2E 都写了不少,但中间集成测试几乎为零。这就好比沙漏,中间细得漏不下去。你单元测试都过了,E2E 也过了,但是模块 A 调用模块 B 时参数格式没对齐——集成测试没测,只能靠 E2E 发现,浪费大量时间。
- 特征:单元 40%、集成 5%、E2E 55%
- 后果:接口兼容性差,现网冒泡多
- 建议:新增模块间接口变更时,先补契约测试(Consumer-Driven Contract)
4.4.3 混乱蛋糕反模式
没有明确分层,想到哪写到哪。一个测试用例里既测了数据库,又调了第三方 API,还检查了前端界面。这种“大杂烩”测试在 CI 里是最恶心的——不可重复、不稳定、没法定位。
4.5 实操建议:怎么落地金字塔
别急着理想化,我建议你按这四步走:
- 盘点现有测试:把你仓库里的 test/ 目录按类型统计,算一下各层占比。如果 E2E 超过 30%,就直接亮黄牌。
- 从最痛的地方改起:挑出 CI 里耗时最长的 3 条 E2E,问自己“这条场景能不能用集成测试覆盖?”。能的话就先拆。
- 约定代码规范:在 CI 中配置覆盖率门槛(单元 ≥ 60%,集成 ≥ 40%),并禁止 CI 中混用测试类型。每个测试文件在文件名上标明类型,比如
xxx.unit.test.ts、xxx.int.test.ts。 - 持续衡量:每两周回顾一次测试耗时、失败率、覆盖率。如果某个模块的单元测试覆盖率低于 50%,就列入技术债务。