1 为什么要关心可测试性
很多公司很多人在实践单元测试中总觉的很困难的一个很重要的原因就是其代码本身不具有可测试性。他们往往会走入一个误区。面对一个几千行、逻辑混乱的方法而抓耳挠腮的想着用十八般武艺,各种框架去写这个方法的单元测试,而最终不得不以失败而告终,耗费了大量的时间却徒劳而无功。其实是他们忽略了这个方法的本身是不具有可测试性的。
因此我们在要对一个方法进行单测之前,一定要先看一下它是不是具有可测试性,如果不具有,那么我们应该先对其进行重构以提高其可测试性。
在可测试的设计中,你应该很容易为代码的每一段逻辑(循环、i语句和 switchi等)快速编写一个单元测试,这些单元测试具有如下属性
- 运行速度快
- 相互隔离,即每个测试可以独立运行,或者作为一组测试的一部分运行,可以按任何顺
- 不需要进行外部配置
- 产生稳定的通过或失败结果。这些就是FCC属性:快速、隔离、无需配置和稳定(Fast, Isolated, Configuration-free,andConsistent)。
如果编写这样的测试很困难,或者需要很长时间,那这个系统就不是一个可测试的,如果把测试看做系统的一个用户,可测试性设计就成为一种思维方式。
2 可测试性设计技巧
如果你要测试的方法真的有几百行上千行,那么我建议先用重构一书中的方法去解决它。其次关于可测试性在实践过程中是有一些技巧的。
2.1 方法可重写
在Java中,方法默认就是虚拟方法,可重写的。而在NET中,如果想要替换一个方法的行为,你需要明确地把方法设置为虚拟方法,才能进行重写。因此我们可以在设计之初就尽量把方法用关键字virtual标记。
2.2 面向接口设计
如果我们的代码都能够保持面向接口设计的原则,意味着我们的依赖都是很容易被替换的,可以让我们在测试中年很容易取创造真实对象的伪对象。
2.3 避免密封类
密封类就是将一个类封闭起来,断其子孙的一种方式。
密封方法则不是为了防止继承而是防止重写,而且它是为了重写基类的虚方法并提供具体的实现,同时防止其后继类(派生类)再次重写该虚方法
无论是我们的被测方法还是其依赖的方法属于一个密封类,那么意味着这个方法是不能被重写的,在测试中也就不能去替换它。因此应该尽量避免使用密封类
2.4 避免在方法内初始化对象
很多人在写代码的时候习惯于在一个方法内部初始化对象,如下面这样
public void GetName(int userId){
//方法内部初始化类
return new User(userId).GetName();
}
这样的设计其实是违背了代码的低耦合原则的,也不具有可测试性,因此我们在开发中应该尽量避免在方法中初始化另一个对象。对于外部依赖的对象可以使用依赖注入的方式。
2.5 避免直接调用静态方法
要在测试中替换一个静态方法的行为,是非常困难的。
要处理这种情况,我们可以使用抽取和重写的方法进行重构,把这个静态方法抽象出去。
一个更为极端的做法是:避免使用任何的静态方法。这样的话,每一段逻辑都是一个类实例的一部分,使得这段逻辑更容易替换。有些进行单元测试或者测试驱动开发的人不喜欢使用单例,原因之一就是单例缺少可替换性。单例是静态的公共共享资源,很难重写它们。
要完全避免使用静态方法可能会过于困难,但是你可以尝试在应用程序中尽量少使用单例或者静态方法,这样在测试时会变得容易一些。
2.6 避免在构造函数和静态构造函数中包含逻辑代码
无论是构造函数还是静态构造函数内的逻辑同样是我们无法在测试中重写控制它的,因此我们需要避免在构造函数和静态构造函数中包含逻辑代码。
关于可测试性设计虽然是有着许多技巧,但如果你掌握了代码编写的内容心法”SOLID原则”,并且熟练应用于编码中,其实你的代码绝大多数都是具有良好的可测试性的。
3 可测试性设计的缺点
可测试性固然可以更加方便的让我们对代码进行单元测试,但有些时候也会给我们带来一些“麻烦”。因此它也是一个颇有争议的话题。有些人认为可测试性是好的设计应该具有的特征之一。也有些认为可测试性会破坏原有的设计带来一些副作用。
那么可测试性设计会带来哪些“麻烦”呢
3.1 工作量
大多数情况下,设计时以可测试性为目标会增加工作量,因为比起不考虑可测试性的设计比起可测试性设计需要编写更多的代码。
3.2 复杂度
可测试性设计有时会让人觉得,它把简单的事情过于复杂化了。有些接口的使用让你感觉很别扭或者设计公开了你未曾考虑过的类行为语义。而且,当使用了很多接口把东西进行抽象之后,如果你要浏览基础代码找到一个方法的真正实现代码,会更加困难和麻烦。
3.3 破坏更好的设计封装
从一些可测试性的技巧我们可以知道有些时候为了让代码更具有可测试性,会破坏一些原有的设计原则。
正如人月神话一书中所说,在软件开发这一行业中没有银弹。一门技术或者方法论在给我们提供了一些帮助的同时也会带来一些问题。这就需要我们在具体使用的过程中****case by case,根据可测试性设计所带来的优缺点找到一个平衡点!