在单元测试培训系列:(一)单元测试概念以及必要性中,我们已经说过单元测试的定义是什么,里面有提到一个很重要的概念:隔离! 是的,没有隔离就没有可测试性,也就没有单元测试。
可测试性Testability
下面我们具体解释下什么叫做可测试性Testability:
让你的代码变的更加松耦合(Loosely coupled),让类与类之间的关联性降低,降低到可以个别独立存在,如此一来便可在彼此互不影响之下完成个别的单元测试,而这些类又能组合成一个有用的应用程式。
因为单元测试要尽可能的隔离与当前方法逻辑没有关系的方法以及外部资源(I/O文件,配置文件,数据库,网络以及静态变量等),即要求每段代码在不依赖其他额外方法以及外部资源的情况下依然可以正确执行!
这么说也许你无法理解也很难想象应该如何做到,那么我们下来举个例子说明。
publicclassTestability { publicdoubleComplexCompute(intx,inty,intz) { doubleresult; CalcServiceWrapper wraper =newCalcServiceWrapper(); var a = wraper.Multiply(x, y); result = wraper.Divide(a, z); returnresult; } } |
以上代码中,可以看到Testability类的ComplexCompute方法中依赖于CalcServiceWrapper类,并调用了该类的Multiply和Divide方法,最终完成了自身的功能。但这个代码就不具有很好的可测试性,因为ComplexCompute无法离开CalcServiceWrapper类而正确编译执行。那么如何重构这段代码使它可以解耦从而具有良好的可测试性呢?依赖倒置(Dependency Inversion)可以做到!
依赖倒置
依赖倒置是一种设计模式,具体的概念大家可以参见:向依赖关系宣战——依赖倒置、控制反转和依赖注入辨析, 在这里就不深入的去阐述依赖注入是怎么回事了。
其实,如果你已经仔细看完那篇文章或者你已经知晓那些知识,那么其实所谓的解耦重构也就很简单了。
依赖注入,有三种实现方式:属性注入,构造函数注入,方法参数注入;在实际项目中,属性注入是最常见的方式,我们下面就来用这种方式来对之前的代码进行解耦重构:
publicclassTestability { publicICalcService CalcService{get;set; } publicdoubleComplexCompute(intx,inty,intz) { doubleresult; var a = CalcService.Multiply(x, y); result = CalcService.Divide(a, z); returnresult; } } |
如上代码中可以看到,把ComplexCompute方法中用到的CalcServiceWrapper类以及实例化的代码去掉,而增加一个类型为ICalcService(其实也可以是CalcServiceWrapper)的公开属性,在ComplexCompute方法中通过调用该属性来实现调用逻辑。这时候我们再来看,有没有发现,CalcService.Multiply方法和CalcService.Divide方法,无论CalcService是否被赋值,被赋予任何对象并不会影响当前方法的编译通过。换句话说:ComplexCompute方法不再依赖于CalcServiceWrapper类型是否实现以及如何实现。