当你看到网上诸多关于单元测试的赞美时,仔细看看你就会发现不少说的实际上是TDD(Test-Driven Development,测试驱动开发),不幸的是大多数人并无注意区分这两个概念。在Writing Great Unit Tests: Best and Worst Practices中,Steven Sanderson强烈表达了本身的观点:Unit testing is not about finding bugs。简单来讲,当先写代码后写单元测试的时候,单元测试就成了一种发现Bug的手段,但做者根据其几十年的开发经验指出这种手段实际上是十分低效的,由于即便每一个功能模块都能正常工做,可是仍然不能保证模块之间、模块与用户环境之间能正确交互,然后者每每是Bug的主要来源。单元测试或许能找到一些Bug,但相比集成测试和系统测试就显得十分低效了。
既然如此,那么单元测试为什么又备受追捧呢?在How Google Tests Software中,三位谷歌的专家介绍了谷歌的软件测试之道,总而言之就是谷歌会在开发之初设计好单元测试(实际上是用代码表达需求),在开发中不断迭代以经过所有的测试(实际上是完成所有需求),最终交付给测试人员的软件已经通过一轮测试,若是还有集成后的Bug,就能够交给专业的测试人员发现了。这是一种典型的敏捷开发,能够看到单元测试扮演更多的是驱动开发的角色。
做为技术标杆的谷歌已经全面引入了单元测试,那么我做为一个普通开发者为何还要提出一番质疑呢?请看下一节。android
在经济学领域,有一个著名的边际收益递减规律,指在投入生产要素后,每单位生产要素所能提供的产量增长发生递减(二阶导数为负)的现象。在本文讨论的场景中,投入产出以下(引自:软件开发过程当中值不值得写单元测试? - voidint's blog):git
成本(投入)github
- 编写单元测试用例所额外付出的时间,短时间内会拖慢项目进度。
收益(产出)数据库
- 提高代码质量。监督开发人员写出更加易于测试和可维护的代码。
- 提高开发团队内部的协做效率。其余开发人员能够经过阅读单元测试用例来理解代码原做者的意图。
- 保证功能实现的长期稳定。代码一旦发生与原功能意图不相符的变化,经过跑单元测试能够体现出来,便可以防止功能被无心识地破坏。
- 提升自动化测试占比,下降其余测试方式上的投入。
在经济学中,边际收益递减现象常出现于产量的短时间分析中。结合对同事的咨询以及本身的调研,这个现象在软件开发领域一样适用。当咱们须要写原型或者开发一个短时间紧急需求的时候,(产品、运营人员)每每要求快速交付,而且因为代码规模有限也每每不会有太多Bug,在这种短时间开发中若是引入单元测试每每会拔苗助长,投入了双倍的时间却没有明显的附加收益。而分析How Google Tests Software一书中最多说起的几个项目(Chrome,Android,Gmail)能够发现,单元测试(更准确说是Test-Driven Development)的成功案例每每都是一些架构设计良好,处于长期迭代开发,基本没有短时间临时紧急需求的产品,项目初期的单元测试每每在几年后还能使用,复用率极高(私觉得复用率某种程度上能够做为是否值得引入单元测试的标准)。而若是一个项目一开始没有引入单元测试、过期和糟糕的代码没有及时重构、临时短时间需求偏多,每每就没有引入单元测试的必要了。后端
Jake Wharton何许人也?答:诸多著名开源项目的做者,Android社区的旗帜人物:
Jake Wharton对于Android平台的单元测试也十分头痛(Against Android Unit Tests),其缘由也是我调研并写下本文的缘由。Android相对于其余开发环境有如下几个特色:网络
能够想象,当你投入大量精力,使用Robolectric、Mockito等框架模拟出一个将数据库数据发日后台的单元测试并经过测试用例后,用户却由于切换网络等小几率场景触发了Bug,你会不会感叹我要这单测有何用?相似Android这种终端环境,其边际收益递减的临界点每每更容易达到,引入单元测试犹需谨慎。架构
结合上面的分析,哪些场景不适合作单元测试已经显而易见了,[When is unit testing inappropriate or unnecessary? [duplicate]](https://softwareengineering.s...:app
- The code has no branches is trivial. A getter that returns 0 doesn't need to be tested, and changes will be covered by tests for its consumers.
在Definition of brittle unit tests中也有详细总结,都有必定参考价值。
此外,有了适合单元测试的场景并不表明就有适合单元测试的代码。在TDD模式中,测试先于开发,因此开发部分的代码接口每每须要通过良好的设计和定义,最好能解耦各个模块,如此开发代码将可以完美匹配测试代码。但这种开发模式每每对开发经验、设计能力要求很高。能都达成此境界的已是TDD的行家了。然而事实是对于没有单元测试经验的开发人员而言,每每没有意识到本身写的代码“不可测试”。如下面伪代码为例:框架
object processObject(Object object) { if (object == objectA) { log.i('error 1 ....') return object; } if (object == objectB) { log.i('error 1 ....') return object; } ..... return object; }
开发人员在Debug的时候,能根据log信息快速定位问题,但对于测试来讲就十分不友好了:返回值都同样。若是想要领会单元测试的优越性,短时间的镇痛与适应彷佛是不可避免的。post
本文没有讨论TDD的各类优点,也没有讨论单元测试的最佳实践,是我的的一些总结,讨论的是单元测试的一些局限之处,或许有不足、有遗漏,又或者彻底错误,欢迎拍砖。譬如,在stackoverflow上有一个关因而否值得作单元测试的问题就由于其争议性而被关闭回答,而又由于有其存在的历史意义而被一直锁定(locked),感兴趣能够看看:
连接:Is Unit Testing worth the effort? - Stack Overflow
本文讨论的只是单元测试(TDD)的局限性,在合适的场景中其做用是巨大的,尤为是轮子级、框架级和开源项目中,了解单元测试也大有裨益。好比,我在为ChatteBot提交代码时,就由于当时不了解单元测试的做用,只修改了代码Bug而没有修改测试代码的错误(测试代码写错真是没救了)。我也所以失去了成为一个5000+ star开源项目贡献者的机会.....