3.1.2一个测试不是单元测试 如果 -
和数据库交互
需要跨越网络
需要访问文件系统
不能和其他的单元测试同时运行
需要配置文件才能运行
3.1.3测试驱动开发和验证(verification)无关,和规范(specification)相关
测试驱动开发并不意味着单元测试驱动的开发。实际上,测试驱动开发这个词非常容易引起误解。在TDD的大佬们眼中,单元测试只是测试驱动开发的工具,而不是目的。测试驱动开发更关注这个代码单元应该如何运行(behave)而不是这个代码单元实现是否正确(verification)。之所以叫测试驱动开发,意义及在于此。最近你可以看到一个新名字的兴起-行为驱动开发(Behaviour Driven Developemnt)。BDD更强调一个组件应该如何工作,以及寻找一个简易的方法来规范行为。
3.1.4为什么需要单元测试?
可以单独地测试每个单元
可以用于验证重构后的代码
可以用来确保不会破坏其他人的代码
可以用来提高系统设计,比如说,不能单元测试的代码将不会出现
3.2 对怎么样的代码做(不做)单元测试?
参考http://blog.stevensanderson.com/2009/11/04/selective-unit-testing-costs-and-benefits/
【摘要】该文作者在总结3年的TDD实践经验的时候,反复认识一个情况:对于某些类型的代码,单元测试工作得很好,也极大的提高了待测代码的质量;但对另外一些类型的代码,单元测试耗费了大量的时间,并没有起到辅助设计和减少缺陷的作用,同时还导致代码更加难以维护。
基于这个想法,作者画了一张图来表示:
作者认为,
(1)代码本身很复杂但是对外部的依赖很少(左上),最适合单元测试,因为耗费较少和收益较多。一般来说,某种算法(排序),核心业务规则,数据解析类似的模块属于这样的代码
(2)带有很多依赖的琐碎代码(Trivial Code with many dependencies,右下):称为协调者(Coordinators),因为这些代码主要用于集成多个代码单元和安排代码单元之间的交互。这些代码不适用于单元测试,因为耗费很高,收益却不大。
(3)代码很复杂而且外部依赖很多(右上):过于复杂的代码,需要重构。
(4)外部依赖较少的琐碎代码(左下):这些代码可测可不测,因为重要性不高,复杂度也不高,加单元测试的意义不是很大。
对于作者的这个想法,我比较同意。正如我在2.2.4和2.2.5中提到的,对于一个在线系统(web-based application)来说,可以分为内外两个圈:内圈由核心领域对象(Core Domain Object)构成,外圈由大量链接汇编代码构成。所谓核心领域对象,这些对象包含核心业务逻辑,数目相对较少,逻辑相对比较复杂,接口(API)相对比较稳定,输入输出相对比较清晰,属于算法类代码,最合适单元测试,也需要单元测试来提高相应的质量。同时我想强调一下:这些对象一定要精心设计,精挑细选,一定要满足数量少,接口稳定,输入输出清晰这三个要求。数量少意味着需要测的代码少,相应的单元测试数量也少。接口稳定意味着用户界面的变化(或需求变化)对单元测试的影响较小。输入输出清晰意味着容易验证逻辑的正确性。相对的,外圈的代码主要用来链接多个领域对象,安排他们之间的交互,转换成用户界面需要的数据结构。也就意味着外圈代码和需求紧密相连,微小的用户界面变化将会导致相应的代码代码,写单元测试得不偿失。
3.3单元测试最佳实践和测试驱动开发反模式
参考http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/
(1)单元测试之间应该完全独立的
不要写不必要的断言,每次只测一个代码单元,模拟(Mock)外部依赖,避免不必要的前提条件。
(2)单元测试应该运行得很快(比如说少于5 min)
单元测试需要运行得很快,这样程序员才愿意经常运行单元测试。这就意味着(1)单元测试不要访问数据库(2)单元测试不要访问网络(3)外部依赖需要被模拟(mock)
(3)给单元测试一个清晰和一致的命名
一个单元测试的名字应该包含3项内容:待测对象_待测案例_期望结果,比如说ProductPurchaseAction_IfStockIsZero_RendersOutOfStockView()。
(4)常见的TDD反模式
撒谎者:所有测试案例都通过了,看上去是有效的。但如果靠近看,你会发现这些案例完全没有测试预期的内容
过度配置:一个单元测试需要一堆配置然后才能开始测试。有的时候需要几百行代码配置环境,设计数十个对象。
巨人:一个单元测试需要测试数千行代码,并且包含大量的测试用例。
无用者 :有的时候模拟是有效的方便的。但是其他一些时候,过多的模拟对象,Stub对象,假对象,导致单元测试主要在测模拟对象而不是实际的系统。
检察官:单元测试对待测代码非常了解,任何对待测代码的变化将影响单元测试代码。
慷慨的富有者:多个单元测试共享测试数据,任何一个单元测试改动了一些数据,其他的测试全部失败了。
本地英雄:单元测试依赖于开发环境某些特定的配置。这意味着:在其他环境上运行单元测试将会失败。
采集者:单元测试验证所有的输出尽管它只对某些数据感兴趣。
狗仔队:单元测试依赖于特定的实现细节,比如说,单元测试捕获待测代码中抛出的每一个异常。
欺瞒者:单元测试验证了一大堆无关的细节,但从不测试核心行为。比如说,待测代码访问数据库并返回数据,单元测试验证每个返回的数据。
大声说话的人:单元测试输出一大堆诊断信息,日志信息,然而没有清晰的成功/失败标志。
please refer tohttp://tdd-antipatterns.net/index.php?title=Main_Page
4. 结论(前途在哪里)
现在,我想答案应该比较清楚了。
第一,重新强调单元测试和测试驱动开发的目地:通过写单元测试来澄清接口(帮助系统设计),确保核心代码(i.e.Domain Object)的正确性。它不是一个检测缺陷的有效工具,也不应该只用覆盖率来衡量单元测试的质量
第二,只对核心代码(比如说主要业务对象)进行单元测试。待测对象需要有相对有限的API数量,比较稳定的接口定义和清晰的输入输出。不要把宝贵的时间花在测试大量的非核心代码,比如说,链接(多个核心对象),协调(多个核心对象),和需求/用户接口紧密相关的代码。
第三,不要滥用单元测试,请时刻关注最佳实践和TDD反模式。
第四,好的单元测试来源于好的设计。设计不好,代码不会好,单元测试也不会好。
最后,我还想继续研究行为驱动开发(BDD),看看BDD是否更实用,请继续关注。
文章来源于领测软件测试网 https://www.ltesting.net/