上一章之中,我们从测试的角度出发,说明了如何写好一个测试,这一章中,我们将要从另一方面,也就是被测试的代码角度出发,讲解一个什么样的代码才是适合被测试的代码。
对于一个代码来说,其必然伴随着时间的增大,带来更多的需求,从而导致复杂度加大。而在复杂度增大之后,如果不做好合理的拆分,那么会导致越来越不好测试。无法对单个模块进行测试,最终只能将整个系统全部启动起来,然后对某一个功能进行测试,那么对于一个可测试性好的系统来说,应该先可以对各个子模块进行测试。然后才进行集成性的测试。
那么怎么样才能写出可测试性好的代码呢
首先,这段代码应该符合设计模式,并且要契合诸如SOLID原则等,比如单一职责原则的代码保证精简。倒置依赖原则的代码,高层就不会依赖于底层实现。
在代码中,我们更是可以看Spring这类IOC框架,其内部的注入,就是因为Spring帮助我们创建对象,并且在类中组装。
完全符合代码理应组装,而不应该在类内部直接创建。
public class TodoItemService {
private final TodoItemRepository repository;
public TodoItemService(final TodoItemRepository repository) {
this.repository = repository;
}
…
}
诸如上面的TodoItem Repository,就可以利用mock创建一个,然后塞入Service对象之中。
其次,在static方法之中,不要涉及到任何的业务代码,虽然static很方便,但是对于测试来说,如果一旦使用了测试,就没法进行mock了。所以static方法之中,理应只存在基础库。
除此外,需要注意,不能直接mock的代码还有Singleton,全局模式这俩者,也需要在编写的时候注意。
除此外,还有第三方代码集成。
对于如何涉及到其他依赖。其他第三方代码,一般来说,我们有两种处理方式,一种是将其抽出,形成static代码。
要么是通过代码隔离,首先定义接口,然后在具体实现中,书写不同的第三方代码调用。就比如
interface TodoItemRepository {
…
}
class FileTodoItemRepository implements TodoItemRepository {
…
}
class DbTodoItemRepository implements TodoItemRepository {
…
}
这样通过定义一个接口,从而隔离不同的第三方代码,从而方便测试。
最后一点是,第三方的代码会回调我们的代码
这种情况下,我们的处理方式一般都是在回调函数中,直接调用我们预先封装好的一个函数,这样的话,就可以针对这个封装好的函数进行调用测试。
这也正是常说的防腐层的概念。
那么我们这里,说了实际上影响测试的主要因素,是在于多个子模块之中,对于子模块代码的书写时,没有兼顾测试性导致只能做整体的测试。
为此我们我说了如何去编写可以测试的代码。
比如组装多于继承。
Static中只存在工具类代码。
多封装接口,使用接口来mock测试。