本书在此列举了一些影响可测性的代码问题。
-
无法初始化一个类。
这种事情往往发生我们想要使用的第三方类库里。如果是自己写的类,发生这种情况可能是由于构造函数对于运行环境有什么特殊的要求(比如要读系统中一个config文件),或者static初始化block干了一些事情,依赖于特定的环境,特定的容器等等。所以一定要注意,不要在构造函数或者静态初始化block中写复杂的对环境有依赖的代码。 -
无法调用一个方法。
一种情况是这个方法是private,外部无法调用。这种情况下,如果我们真有测试一个内部方法的要求,同时又不想把这个方法暴露在外面,可能说明这个方法过于复杂,正确的做法是我们要把这个方法的内容抽到一个单独的类中去,这个类上就可以有公共方法,我们写测试来测试这个类。 -
无法观察代码的输出
对于有返回值的方法,我们能观察返回值。但有些代码的行为本身没有返回值,需要assert的行为是它和其他对象的正确交互。如果这个类没有提供用替身替换这些对象的方法,就会导致没法观测代码的行为。 -
无法替换一个合作的类
代码中出现了用new MyClass()来直接构造对象并赋值, 这种情况下,MyClass无法被替换了。第二种情况是代码中出现方法链, getA().getB().getC().getD() 这样,会导致你需要mock一整条链上的对象,不是不能替换,但是会非常麻烦。 -
无法替换一个方法
static和final, 其实大部分业务代码都不需要用到的,除非写一些很核心的类库。如果无聊用了,往往是一种过度保护,得不偿失。private则不能过于复杂,过于复杂的一定要用protected方法或者放到另一个类中作为公共方法。
下面是提高可测试性的一些建议
- 不要写过于复杂的private方法。private方法可以是比较简单的不需要测试的util方法。那么非public的复杂逻辑不能放private方法,放哪里呢?你可以把复杂逻辑抽到另一个类的公共方法里去。这个类的存在可以是只为另一个类服务的,这没有什么问题。
- 不要把方法标注成final。除了开发核心类库,业务代码基本上没有任何合理理由把方法标为final。
- 少用static标注方法。
- 少用 new来直接创建对象。需要考虑是不是会需要在测试中替换这个对象的实现。
- 不要在构造函数中写复杂的逻辑。如果真要这么做,把这部分逻辑放到可被重写的protected方法里,这样别人至少可以用子类来替换掉这部分逻辑。
- 尽量不要用singleton。非要用singleton,请返回一个接口而不是实例。
- 用组合(composition)而不是继承来复用代码。继承的主要作用是实现多态性,而不是代码复用。
- 如果某外部类库比较难以测试,你可以用自己的接口来包装外部类库。
- 不要用Service Lookup模式(通过某个static方法得到一个Singleton实例,想起TMS中无处不在的Beans.get方法)。这样只是让代码看上去整洁了,但是会造成很多隐式的引用,对测试非常不友好。
网友评论