上周末去听了Scrumgathering的试讲活动,感觉此类活动还是挺有意思的,一群scrum实践者或者爱好敏捷的同学在一起讨论如何做好敏捷项目,这次主要听了一场关于如何对遗留代码做单元测试的演讲,因此向记录一下一些很好的观点,来用于我们对单元测试的理解,以及如何提高代码可测性。
所谓的遗留代码(legacy code),简单就是指没有任何测试的代码。那么我们如何来对这些遗留代码进行测试,或者是通过修改使这些遗留代码能够变的更加testability。
案例一、
public class Car{
private Engine engine;
public Car(){
engine = new Engine(10);
}
public boolean isMove(){
return engine.speed()>0? true:false;
}
}
针对这个class的test case应该是这样的:
public class TestCase{
private Car car;
public void testMove(){
car = new Car();
Assert.assertEquals(true,car.isMove());
}
}
对于这个测试用列,我们并没有真正的达到测试Car的isMove方法的正确性,因为Car在这里对Engine类的一个依赖,在这个测试用列中我们没法达到测试isMove方法中的逻辑。因此我们需要对Car这段遗留代码进行解依赖。
对Car这个类的构造函数进行修改
public class Car{
private Engine engine;
public Car(Engine engine){
this.engine = engine;
}
public boolean isMove(){
return engine.speed()>0?true:false;
}
}
测试代码如下:
public class TestCase{
public Car car;
public void testIsMove(){
Engine engine = new MockEngine(10);
car = new Car(engine);
Assert.assertEquals(true,car.isMove());
}
}
可以看到这时的测试代码已经完完全全的在测试isMove方法了,我们不需要关心Engine,我们可以随便mock一个engine对象。这样就完成了对Engine的解依赖,使代码具有可测性。
案例二、
提高方法的可见性,如果实际工作中我们需要对一个private的方法进行测试的话,我们可能会无从下手,对于这类测试,我们可以说是该方法无法在测试工具中导入,那么这个时候,我们需要提升一下方法的权限,比如我们可以将private方法改为protected,那么我们就可以通过子类继承被测类,子类中对protected方法是可见的,那么我们就可以将子类导入到测试用具中完成测试工作。
案例三、
虽然目前已经有很多的mock工具,如jmock, easymock等工具,这些工具可以很方便的构造mock类,但是这些类的可读性会比较差,因此提倡自己写mock类,这样不但可以增加可读性,并且对于mock类的实现我们可以比较自由。
案例四、
对于较大类的解决,在实际工作中我们可能会遇到一个非常庞大的类,这个类可能有几百个方法,那么如何提高这种类的可测性,提倡一个原则(单一职责原则SRP),每个类应该仅承担一个职责:它在系统中的意图应当是单一的,且修改它的原因应该只有一个。
案例五、
如果需要在遗留代码中添加一些新的功能,一些非常简单的功能的时候我们应该怎么做,如下代码,我们需要在parse方法之后加上日志记录,那么我们可以通过以下几种方法来做:
public class Parser{
public void parse(){
.........
}
}
我们可以添加一个方法叫做parseWithLogger().
如:
public class Parser{
public void parseWithLogger(){
Logger loger = Logger.log(.....);
parse();
}
public void parse(){
......
}
}
这样我们不需更改原来的parse方法进行修改,我们之需要增加一个新的方法,然后在我们测试用具中对这个新的方法进行测试就行。
我们也可以这样更改代码,把原方法改名为parseWithoutLogger().
public class Parser{
public void parseWithoutLogger(){
...........
}
public void parse(){
Logger log = Logger.log();
parseWithoutLogger();
}
}
这样对代码的改动也不是很大,我们可以在测试用具中测试parse方法是否加logger。
两种方法对遗留代码都不会造成大改变,我们强调对遗留代码的修改一定要小心,因为遗留代码是没有测试代码,我们不能大刀阔斧的进行修改,只能小步前进,然后对遗留代码加上测试用列,保证重构的正确性。
当然对遗留代码的可测性提高还有很多方法,可以参考Michale Feathers 写的<working effectively with legacy code>.同时也非常感谢@姚若舟 周末分享的How to write unit test for new code based on legacy code。 非常精彩的演讲。