最近看到一篇介绍软件测试相关的文章,文章虽然比较早(2018年的),但是其中的观点依然适用。
在目前AI盛行的时候,软件测试也必然迎来它的变化。
我把文章中的要点整理了一下,这些测试的反模式涵盖了从测试策略、代码质量到心态管理的各个方面。
原文的链接放在了文章的末尾。
1. 只有单元测试,没有集成测试 (Having unit tests without integration tests)
- 问题:即使所有独立的类/方法都通过了测试,但在实际运行时,组件之间的连接(数据库、网络、外部API)可能会失败。
- 盲点:单元测试无法覆盖跨切面关注点(Cross-cutting concerns),如事务处理、SQL触发器、API契约不匹配等。
- 建议:除非是完全隔离的工具(如命令行计算器),否则必须引入集成测试来验证系统各部分的协作。
2. 只有集成测试,没有单元测试 (Having integration tests without unit tests)
- 问题:通常由认为"单元测试没用"的开发者造成。
- 代价 :
- 复杂度爆炸:要覆盖所有代码路径的组合,集成测试的数量会呈指数级增长,难以维护。
- 执行极慢:反馈循环太长(从几秒变为几分钟甚至更久),降低开发效率。
- 调试困难:测试失败时,难以快速定位是哪个组件出了问题。
- 建议:遵循测试金字塔,用大量单元测试覆盖业务逻辑,用少量集成测试覆盖组件交互。
3. 只有错误类型的测试 (Having the wrong kind of tests)
- 问题:盲目照搬"测试金字塔",而不考虑应用的实际类型。
- 场景区分 :
- 计算型工具(如Linux命令):应以单元测试为主。
- 支付网关(重外部交互):应以集成测试为主。
- 网站生成器(重交互体验):应以UI测试为主。
- 要点:根据业务价值决定测试类型,而不是死守一种形状。
4. 测试错误的功能 (Testing the wrong functionality)
- 问题:为了追求覆盖率而测试无关紧要的代码(如简单的 Getter/Setter),却忽视了核心风险。
- 策略 :建立"代码严重性"心理模型:
- 关键代码 (Critical) :经常变更、经常出错、业务影响大 -> 必须覆盖。
- 核心代码 (Core):偶尔变更 -> 尽量覆盖。
- 其他代码 (Other):几乎不变 -> 没必要浪费时间。
- 建议:优先覆盖那 20% 导致 80% 问题的关键代码。
5. 测试内部实现细节 (Testing internal implementation)
- 问题 :测试与代码的实现方式 紧密耦合,而不是验证行为。
- 后果:重构代码(Refactoring)时,即使业务逻辑没变,测试也会报错。这会导致开发者认为"测试是累赘"。
- 建议:测试应该关注输入和输出(行为),而非对象内部的状态或私有字段。
6. 过度关注代码覆盖率 (Paying excessive attention to test coverage)
- 问题:将覆盖率(如 100%)视为唯一的质量指标,导致出现大量为了刷数据的低质量测试。
- 观点:覆盖率只是一个参考数字。高覆盖率不代表无 Bug。
- 更好的指标 :
- PDWT:写测试的开发人员百分比(应为100%)。
- PBCNT:生产环境 Bug 转化为新测试的百分比。
- PTD:确定性(非不稳定)测试的百分比。
7. 拥有不稳定或缓慢的测试 (Having flaky or slow tests)
- 问题:测试时过时挂(Flaky),导致团队失去信任,忽略测试结果。
- 来源:通常是集成测试或 UI 测试,源于环境不稳或异步处理不当。
- 建议:必须修复或隔离不稳定测试。测试套件必须是绝对可靠(Deterministic)的,任何失败都应代表真实的代码问题。
8. 手动运行测试 (Running tests manually)
- 问题:依赖人工触发测试或人工准备环境,无法实现真正的 CI/CD。
- 目标:所有与代码正确性相关的测试(单元/集成)必须完全自动化。
- 标准:开发者提交代码后,应在 5-15 分钟内自动获得反馈。QA 应专注于设计新测试,而不是充当"测试运行器"。
9. 将测试代码视为二等公民 (Treating test code as a second class citizen)
- 问题:生产代码写得很漂亮,测试代码却充满了复制粘贴、硬编码和糟糕的设计。
- 后果:测试代码难以维护,修改成本高昂,最终被抛弃。
- 建议:对测试代码应用同样的工程标准(DRY, KISS, SOLID)。重构测试代码,提取公共库及 Helper 方法。
10. 不将生产环境 Bug 转化为测试 (Not converting production bugs to tests)
- 问题:修复了 Bug 但未添加回归测试,导致同样的问题在未来重复出现。
- 价值:源自生产环境的 Bug 测试价值极高,因为它们直接对应了真实世界的痛点和高风险区域。
- 规则:每一个线上 Bug 的修复,都必须伴随一个能复现该 Bug 的测试用例。
11. 将 TDD 奉为宗教 (Treating TDD as a religion)
- 问题:教条式地要求所有代码都必须"先写测试再写实现"。
- 现实:在探索性编程(Spike)、原型设计或极早期初创阶段,后补测试或不写测试也是合理的策略。
- 建议:TDD 是一个好工具,但不是唯一的真理。根据项目阶段和需求清晰度灵活选择。
12. 写测试不看文档 (Writing tests without reading documentation first)
- 问题:因为不熟悉测试框架,自己造轮子写各种笨拙的"工具方法"。
- 后果:测试代码晦涩难懂,非标准化的写法增加了新人的学习成本。
- 建议:深入学习测试框架的功能(如参数化测试、Mock 工具、Setup/Teardown 机制),使用标准库而非自制土方。
13. 因无知而败坏测试的名声 (Giving testing a bad reputation out of ignorance)
- 问题:开发者因为经历过上述反模式(如经历过极慢的测试、脆弱的测试),从而彻底否定测试的价值。
- 观点:不要因为错误的测试方式(Bad Habits)而否定测试本身。
- 建议:识别并承认过去糟糕的测试体验是由于"反模式"造成的,在新项目中采用正确的实践。