前端工程化(七):测试体系怎么分层与取舍
测试体系不是把所有场景都写成 E2E,也不是追求测试数量越多越好。应让确定性强、反馈快的测试覆盖底层逻辑,让组件、契约和集成测试覆盖协作边界,再用少量 E2E 验证关键用户路径。测试结果还要区分通过、断言失败和环境阻断,避免把环境问题误判成业务结论。
四类测试分别验证什么
单元测试验证最小函数或模块的输入输出。它通常不依赖网络、数据库或浏览器,执行速度快,失败后容易定位到具体规则。例如一个权限判断函数、金额计算函数或参数转换函数,都适合优先用单元测试覆盖。
组件测试关注组件渲染、状态变化和用户交互。它比单元测试更接近真实界面,但通常仍然会隔离网络和外部服务。表单输入、按钮禁用、错误提示、加载状态和局部交互,都可以在组件层验证。
契约测试验证前后端或多个服务之间约定的接口边界,包括请求参数、响应字段、类型、状态码和错误结构。它能较早发现服务方的破坏性变更,也能让调用方确认自己依赖的协议仍然成立。契约测试的成本通常低于启动完整系统做 E2E,但它不能验证完整业务流程。
E2E 测试从用户视角验证跨页面、跨服务的关键流程,例如登录后完成一次核心提交、支付或发布。它能验证页面跳转、接口协作、权限和最终结果,但依赖更多环境,执行更慢,失败后的定位也更困难。
测试金字塔不是固定比例
测试金字塔通常把单元测试放在底层,把组件、集成和契约测试放在中间,把少量 E2E 放在顶部。底层测试数量可以更多,因为执行快、反馈快、定位容易;越接近真实用户,通常越依赖环境,成本也越高。
测试金字塔不是要求所有项目都遵守某个固定数量比例。它真正提醒我们根据反馈速度、业务风险和维护成本分配测试。一个项目如果单元测试很多,却没有覆盖关键接口协作,仍然可能缺少有效保障;如果所有场景都用 E2E,则会让反馈变慢,失败定位变困难。
冒烟测试和回归测试的区别
冒烟测试是范围较小、速度较快的关键路径筛选,用来判断一个版本是否值得继续测试。例如部署后检查登录、核心页面打开和一次最关键的提交流程。
回归测试范围更广,关注本次改动有没有破坏原来已经正常工作的行为。它可以由单元、组件、契约、集成和 E2E 等多层测试共同组成。
同一个测试脚本可以根据执行集合承担不同角色。新版本发布前,它可能被纳入冒烟集合;完整回归时,它也可以作为回归测试的一部分。因此不能只看“第一次执行”还是“后续执行”,还要看测试验证对象、外部依赖和团队如何编排测试集合。
如何治理脆弱测试
容易脆弱的测试往往依赖真实网络或第三方服务、固定等待时间、共享数据库状态、测试执行顺序、随机数、当前时间和易变的 CSS 选择器。
治理时可以采用几条原则:用隔离 fixture 或 mock 控制外部依赖;等待明确的状态条件,而不是固定 sleep;每个测试准备并清理自己的数据;使用稳定的测试选择器;控制时间和随机性;把真实第三方服务验证限制在少量专门测试中。
测试应该优先覆盖什么
测试优先级要围绕风险,而不是围绕代码行数。表单或 API 流程中,值得优先覆盖的通常包括必填校验、权限边界、金额或状态转换、成功与失败分支、重复提交、重试和关键用户流程。
纯展示样式、框架已经保证的基础能力,以及同一个行为在多个层次完全重复覆盖,通常不值得反复测试。取舍时可以考虑业务风险、变更频率、失败代价和测试维护成本。
my-world 的真实测试脚本
当前 my-world/package.json 中有多个分散的 test:* 脚本,覆盖安全守卫、流式、Embedding、pgvector、OSS 和 Markdown 等验证,但没有统一的 test 聚合入口。
其中 test:server-action-guard 的脚本内容是:
"test:server-action-guard": "node --import tsx scripts/test-server-action-guard.mjs"它直接导入 src/lib/security/serverActionGuard.ts,用固定输入断言允许和拒绝场景,不需要启动 API、连接数据库或访问网络,因此属于确定性较强的单元 / 逻辑测试。实际执行:
npm run test:server-action-guard
PASS Server Action 请求防护(12/12)
退出码:0这次通过只能证明脚本列出的 12 个固定输入断言符合预期,不能外推到未覆盖输入、其他调用方、API 集成、数据库或完整用户流程。
test:embedding-pgvector 则读取 DATABASE_URI 连接 PostgreSQL,检查 pgvector 扩展,创建临时表并执行向量距离查询。它更接近数据库集成或冒烟验证,依赖数据库、扩展和环境配置;脚本本身不调用 Embedding Provider。缺少前置环境时,它会返回 blocked,这不能等同于断言失败,也不能算测试通过。
如何设计最小测试门禁
提交后的最小门禁可以先选择不依赖外部环境、执行快且结果确定的测试,例如 test:server-action-guard,再结合 lint、TypeScript 类型检查和构建检查。需要数据库或第三方服务的脚本放入相应的集成验证阶段,并明确环境缺失时的处理方式。
测试通过、断言失败和环境阻断必须分开记录:
- 通过:当前测试用例的断言满足预期。
- 失败:在可执行的环境中,代码行为不符合预期。
- 阻断:测试前置条件不满足,无法完成验证,不能判断功能已经通过,也不能直接判定功能失败。
有想法或建议? 联系我