《C++代码整洁之道:C++17 可持续软件开发模式实践》 —2.3 单元测试
2.3 单元测试
没有测试的“重构”不能称之为重构,它仅仅是到处移动垃圾代码。
—Corey Haines (@coreyhaines), December 20, 2013, on Twitter
单元测试是一小段代码,在特定上下文环境中,单元测试能够执行产品的一部分代码。单元测试能够在很短的时间内,展示出你的代码是否达到了预期的运行结果。如果单元测试覆盖率非常高,那么,你就可以在很短的时间内,检查正在开发的系统的所有组件是否运行正常。单元测试有许多优点:
大量的调查和研究已经证明,在软件部署运行以后修复bug的代价,比在单元测试阶段修复bug的代价要高得多。
单元测试能够给出关于整个代码库(已经写了单元测试的那一部分代码)的即时反馈,如果单元测试覆盖率足够高(大约100%),开发人员在几秒钟内就能知道代码库中的代码是否能够正常运行。
单元测试让开发人员有足够的信心重构代码,而不必担心因重构而带来的错误。事实上,没有单元测试的重构是非常危险的,严格来讲,不能称之为重构。
高覆盖率的单元测试,可以有效防止陷入耗时和让人手足无措的代码调试中,可以大大降低长时间使用调试器调试的问题。当然,没有人能够完全避免使用调试器调试。调试器可以用来分析细微的问题,或者找出执行失败的单元测试的原因。但是,调试器不应该是确保代码质量的关键工具。
单元测试是一种可以被执行的产品文档,因为单元测试精确地展现了代码是如何被设计和使用的。可以说,单元测试是一组非常有用的示例代码。
单元测试可以很容易地检测回归测试的代码,也就是说,单元测试能够很快地检查更改代码后引发的异常。
单元测试可以促进实现整洁且良好的接口,可以帮助开发人员避免文件间不必要的依赖关系。可测试性的设计也是良好的可用性的设计,也就是说,如果一段代码可以很容易地与测试夹具集成,那么,这段代码通常也可以很容易地集成到产品的代码。
单元测试能够促进开发。
上述提到的最后一个优点看似是矛盾的,这里做一下解释,单元测试能够促进开发—这似乎是不可能的事情,也不合乎正常的逻辑。
毫无疑问,编写单元测试意味着成本的投入。首先,最重要的是,管理者只看到了这种成本的投入,却并不明白为什么开发人员应该为测试投入时间。特别是在项目的开始阶段,单元测试对开发速度的促进几乎是看不见的。在项目的早期阶段,当系统的复杂度较低并且大部分组件都工作得很好的时候,编写单元测试看起来只是无意义的付出。但是,时代正在改变……
当系统变得越来越庞大(超过100 000行代码量)且系统复杂度增加时,理解和验证系统变得越来越困难(还记得我在第1章描述的软件熵吗?)。通常,当不同开发团队中的许多开发人员协同开发一个庞大的系统时,他们每天都要面对其他开发者编写的代码,如果没有单元测试,这将成为一项令人沮丧的工作。我确信,团队中的每个人都知道那些愚蠢的、无休止的调试,在单步模式中一遍又一遍地调试代码,同时一次又一次地分析变量的值……这非常浪费时间!并且,这也将大大降低开发速度。
特别是在软件开发的中后期,以及在产品交付后的维护阶段,良好的单元测试会展现出它们积极的一面。在编写单元测试后的几个月或几年里,当一个组件或产品的API需要更改或扩展的时候,单元测试能够最大程度地节省时间。
如果单元测试覆盖率很好,那么开发人员编辑一段自己写的代码或别人写的代码,影响不会太大。良好的单元测试有助于开发人员快速理解另一个人编写的代码,即使这段代码是在三年前编写的。如果单元测试失败,通过失败的信息,能够准确知道失败的地方。开发人员可以相信,如果所有的单元测试都通过了,那么所有的函数都可以正常运行,烦人的调试就会变得不常见。调试主要用于分析那些错误现象不直观的失败的单元测试,这将是一件很有趣的事情。单元测试具有正向的促进作用,能给我们带来更快更好的结果,开发人员也将对基础代码有更大的信心,并对此感到满意。如果更改需求或加入新的特性呢?也没有问题,因为单元测试能够快速、频繁地完成产品的单元测试,并且能够保证产品的质量。
单元测试框架
C++的单元测试框架有很多种,例如:CppUnit、Boost.Test、CUTE、Goole Test等。
一般而言,几个单元测试框架的集合称为xUnit,所有遵循所谓的xUnit的基本设计的单元测试的框架,其结构和功能都是从Smalltalk的SUnit继承而来的。抛开实际情况不谈,本章内容没有涉及某个单元测试框架,因为本章内容适用于一般的单元测试,单元测试框架完整而详细的对比内容将超出本书的范围。进一步讲,选择一个合适的单元测试框架取决于很多因素。例如,如果以最小的工作量和最快的速度添加新的单元测试,对你来说是非常重要的,那么,最小的工作量和最快的速度将成为你选择单元测试框架的主要因素。
- 点赞
- 收藏
- 关注作者
评论(0)