很多人做过单元测试,可能对单元定义没有较真过,其实普通小名词蕴含大概念。
Martin Fowler在其 “单元测试”一文中对单元定义是一个类,一个类中可能有很多方法行为,单元不能粒度太细,也不能太粗,太细了就容易范DHH批评的宗教原教旨主义,也就是形式主义,影响了正常软件设计意图,太粗了则达不到持续交付的目标,软件BUG频出。
这场讨论是来自Rails之父DHH提出“TDD已死”的讨论,主要是针对单元测试在Web框架比较难以实施,因为普通Web框架中就是一个MVC框架,Model实际是数据库,在MVC结构下单元测试无法插足,一般是使用变种的Controller,这使得C控制器的含义变成两个,一个是MVC的控制器,用于数据库模型Model和视图View的交互;另外一个专门用于对模型Model测试的,”TDD已死“派认为这样不妥,破坏了控制器原本设计含义。
其实,banq我个人意见还是坚持以前的想法,这种尴尬是因为MVC这种不合时宜的结构模式造成的,我2010年曾经写了一篇:MVC模式已死,里面实际谈到了MVC模式的僵化,建议以DCI等架构,当然包括后来的DDD/CQRS来替代,从今天观点来看,这样才能真正避免MVC模式和单元测试的冲突。
Ruby阵营的另外一位牛人Andrzej Krzywda发表了TDD and Rails - what makes a good Unit?更加佐证了我当初的观点。
在这篇文章中,AK认为单元的定义需要从你采取的技术架构上定义,在你应该知道的四种架构一文中已经谈及了六边形Hexagonal架构 干净的架构和DDD/CQRS以及DCI。
AK举了一个例子,如果你有订单Order, 一个订单中有很多订单条目OrderLines, 以及一个货运地址ShippingAddress和一个客户Customer. 那么你是否为这四个对象建立四个单元测试呢?很显然不是,而是只要为Order这个类建立一个单元测试就可以了,为什么呢?因为通过Order对象测试已经可以掌握整体了。测试可能从未知道ShippingAddress的存在。因为这是单元Order的内部实现细节。
所以,一个类并不是一定是一个好的单元,通常是一组类。单元的定义是为了让你测试,而不管被测试单元内部如何变化,这是单元测试的设计目标,这样你在每次重构时就不必改变测试代码了。
从DDD角度看,Order其实是一个聚合根,也就是说,单元测试中单元是指为每个聚合根做一个测试就可以了。一个DDD aggregate聚合是一组领域对象,能够被看成一个单元就可以了。
在“干净的架构”中有一个用例的概念有所涉及,是通过一组操作来定义的。
在“六边形架构”中有关于一个单元周围被适配器adapters围绕, 这个单元通常称为Middle Hex。
在“DCI”架构中,DHH引述James Coplien的TDD的谈话。James已经小有名气,不仅从他对TDD有过强烈推动和意见,而且更多的他在DCI世界的活动。他是这项运动的创始人之一。 DCI是这里最鼓舞人心的架构。Ruby和DCI其实是一个梦幻般的组合。 DCI提供了良好的工具用于定义一个单位是什么。定义一个上下文为一个单元,一个单元是一组对象的协作的范式。
[该贴被banq于2014-05-08 09:29修改过]