测试策略实战:从单元测试到集成测试的工程方法
测试不是"测了就比不测好",而是"测什么、怎么测、测多少"的问题。本文从测试金字塔出发,讨论单元测试、集成测试、端到端测试的职责边界和覆盖率策略,以及如何让测试成为开发流程的一部分而不是负担——适合正在搭建或优化测试体系的团队。
先说结论:测试的关键不是”测了”,而是”测对了”
很多团队把测试当成”上线前的安全检查”,为了赶进度压缩测试时间,或者为了指标盲目追求覆盖率。这两种做法都偏离了测试的本质——测试是开发流程的一部分,不是门禁。
本文从测试金字塔出发,讨论每种测试的职责边界和落地方法。
1. 测试金字塔
/\
/ \ E2E 测试(少而精)
/ \
/------\
/ 集成测试 \ (关键路径覆盖)
/------------\
/ 单元测试 \ (多而快,覆盖核心逻辑)
/----------------\
各层职责
| 层级 | 速度 | 数量 | 职责 |
|---|---|---|---|
| 单元测试 | 毫秒级 | 多 | 验证单个函数/类的行为 |
| 集成测试 | 秒级 | 中 | 验证模块间交互 |
| E2E 测试 | 分钟级 | 少 | 验证关键用户流程 |
2. 单元测试
2.1 测什么
- 业务逻辑(计算、校验、转换)
- 边界条件(空值、越界、异常输入)
- 错误处理(函数在异常情况下是否返回正确的错误)
2.2 不测什么
- 框架行为(Express 的路由、React 的渲染——框架自己已经测过了)
- 数据库查询(那是集成测试的事)
- 第三方 API 调用(Mock 掉,只验证调用参数和返回值处理)
2.3 示例
// 待测函数
function calculateDiscount(amount: number, level: 'bronze' | 'silver' | 'gold'): number {
const rates = { bronze: 0, silver: 0.1, gold: 0.2 };
return amount * (1 - rates[level]);
}
// 测试
describe('calculateDiscount', () => {
it('returns full amount for bronze', () => {
expect(calculateDiscount(100, 'bronze')).toBe(100);
});
it('applies 10% discount for silver', () => {
expect(calculateDiscount(100, 'silver')).toBe(90);
});
it('applies 20% discount for gold', () => {
expect(calculateDiscount(100, 'gold')).toBe(80);
});
it('handles zero amount', () => {
expect(calculateDiscount(0, 'gold')).toBe(0);
});
});
3. 集成测试
3.1 测什么
- API 接口的请求和响应
- 数据库读写操作
- 服务之间的消息传递
3.2 配置
// 使用测试数据库
beforeAll(async () => {
await db.migrate.latest();
await db.seed.run();
});
afterAll(async () => {
await db.destroy();
});
describe('POST /users', () => {
it('creates a new user', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'test', email: 'test@test.com' });
expect(res.status).toBe(201);
expect(res.body.name).toBe('test');
});
});
4. 端到端测试
4.1 测什么
- 关键用户流程(注册、登录、下单)
- 跨系统的交互(前端 → API → 数据库 → 第三方服务)
4.2 原则
- 只测关键路径,不测所有路径
- 用 Cypress 或 Playwright
- 在 CI 中每天跑一次,或者在合并到主干前跑
5. 测试策略总结
| 测试类型 | 覆盖目标 | 运行频率 | 允许失败? |
|---|---|---|---|
| 单元测试 | 核心逻辑 80%+ | 每次提交 | 不允许 |
| 集成测试 | 关键 API 路径 | 每天 | 不允许 |
| E2E 测试 | 关键用户流程 | 合并前 | 可以(需人工确认) |
测试策略的成熟度不看”有多少测试”,而看”测试在多大程度上融入了开发流程”。 最好的状态是:写测试和写代码一样自然,不写测试反而觉得不习惯。
需要测试体系建设或技术咨询?联系我们,说清你的项目规模与技术栈,24 小时内回可行性。
相关阅读
- Code Review 实战:从 Checklist 到工程文化的落地方法 —— 测试与 Review 共同构成质量保障体系
- 技术方案评审怎么做:选型、架构评审与可行性报告的方法 —— 测试策略的上层决策框架
- LLM 应用评测实战:评测集、LLM-as-a-Judge 与发布门禁 —— 传统测试与 LLM 评测的衔接:确定性的部分用断言,概率性的部分用评测集
常见问题
测试应该覆盖多少代码?
不要追求 100% 代码覆盖率。核心逻辑(业务规则、数据处理、API 接口)应该覆盖 80% 以上,UI 层和胶水代码覆盖 50% 就够了。重点不是覆盖率数字,而是"关键路径有没有测试"。一个只有 60% 覆盖率但核心逻辑全测了的项目,比一个 90% 覆盖率但核心逻辑没测的项目好得多。
单元测试和集成测试的分界线在哪?
单元测试不依赖外部系统(数据库、API、文件系统),所有外部依赖都 Mock 掉。集成测试验证模块之间的交互是否正常,依赖真实的外部系统(测试数据库、测试 API)。分界线:如果你的测试需要启动数据库,那就是集成测试,不是单元测试。
测试太慢怎么办?
分层运行:单元测试每次提交都跑(几秒到几十秒),集成测试每天跑一次(几分钟),端到端测试在合并到主干前跑(十几分钟)。慢的测试不是不写,而是不阻塞开发流程。
TDD(测试驱动开发)真的有用吗?
有用,但需要练习。TDD 的核心价值不是"先写测试再写代码",而是"写代码之前先想清楚预期行为"。如果你刚开始尝试 TDD,建议从 bug 修复开始——先写一个重现 bug 的测试,再修复代码,测试通过。这样既有成就感,又能确保 bug 不会再回来。