编码与测试
本文取材自《The missing readme》-- Chapter 3/6
混乱的根源
1/ 软件的熵
代码走向无序的趋势被称为软件的熵(entropy)。
很多事情都会导致软件的熵:
- 不同开发者代码、风格上的差异;
- 不断进步的技术栈和不断发展的产品需求导致的混乱
- bug 修复和性能优化带来的复杂度。
解决:lint、code review、持续的重构
2/ 技术债
技术债是导致软件的熵的一个主要原因,指为了修复现有的代码不足而欠下的未来的工作。
技术债是不可避免的;技术债甚至可能是成功的标志:项目只有存活了足够长的时间,才会变得无序。
只有成功的软件才会被人喷
何安全的修改代码
1/ 定义变更点
定位需要改变的代码,阅读代码,进行实验并提出问题。
2/ 寻找测试点
即代码被测试用例调用和注入的区域。测试点揭示了代码在被你变更前的行为,你需要使用这些测试点来测试你的变更。
3/ 打破依赖关系
如果你发现没有免费的测试点,就得打破依赖关系找到他们。
- 将一个大的复杂的方法拆分成多个小的方法,以分别测试独立的 feature;
- 引入一个接口(或其他中介),为测试提供一个复杂对象的简单实现 -- 不完整,但要满足测试需要;
- 注入明确的控制点,模拟难以控制的执行的切片,如时间的推移;
- 不要为了测试而破坏封装;
修改代码的过程中记得经常运行测试用例
测试用例跑得太慢很痛苦...
4/ 编写测试
当你重构和打破依赖关系时,应该添加新的测试来验证旧的行为。考虑使用自动化测试工具来生成和捕获现有行为的测试用例。
5/ 进行修改和重构
有了良好的测试用例后,就可以“真正”地变更代码了,添加测试用例来验证这些变更,然后重构代码以进一步改善其设计。你可以大胆的改变,因为已经确保了代码的边界。
开发时可以频繁的 commit,一方面方便撤销修改,另一方面作为远程备份。但在 PR 前记得重置分支并压缩提交。
如何编写测试
测试的用途
- 保护代码不会被未来无意中的修改所影响;
- 促成清爽的代码;
- 强迫开发者「试用」他们设计的 API;
- 记录组件件如何交互;
- 作为做实验的「游乐园」。阅读测试用例来了解一个陌生的程序,以及遇到 Bug 或开发问题时,通过编写测试用例来了解它们。
其中,促使清爽的代码的论据如下:
编写测试会尽早的暴露笨拙的接口设计,事实上,「意面式」的代码,或者有很多依赖项的代码往往很难测试。编写测试可以促使开发人员分别通过改进关注点分离和降低紧耦合的方式来确保他们的代码拥有良好的构造。
测试其实是一种形式的文档,说明了代码是如何交互的。
编写干净的测试
编写测试时要像编写其他代码一样「谨慎」,简单来说,你的各种编程最佳实践同样也要用在测试代码上。
专注于测试基本功能而不是实现细节,好的测试用例需要在代码重构后依然正常运行。如果测试代码和实现细节结合得太紧密,测试代码会经常失败,但却不意味着代码坏掉了,只表示代码改变了,这不提供任何价值。
想起来之前写测试用例时,有时会为了方便而专门为测试开接口取数,其实这个可能意味着测试代码有问题或者代码本身有问题。
避免过度测试
不要为了提高代码覆盖率而去提高代码覆盖率,追求特定的代码覆盖率是一种短视行为。
编写测试是需要付出的,把精力放在最高价值的测试上。可以用风险矩阵来寻找需要关注的领域,产生最大的收益:
图中,纵向为失败的可能性,横向为失败的影响,测试会让代码的风险向左下方转移。首先应当关注代码中的高风险区域,不要为不值得测试的代码编写测试。
感觉浪费时间倒问题不大,主要是过度测试容易陷入 bike-shedding,在琐碎的低价值事情上的消耗让我们无意忽略了真正重要的事情