👆Spock
支持第三种虚拟对象:spies
。Spy
对象中一部分是真实方法,一部分是虚拟方法。之所以不在第六章介绍是因为Spy
具有争议性——意味着代码本身有问题。通常使用在无法重构的遗留代码中。
例子
现有一安全工具——从外部录像机获取视频、检测闯入者并从硬盘从删除证据。应用由Java
实现,第一个类负责删除文件,第二个类使用人脸识别算法检测敌人:
public class CameraFeed {//获取录像帧
[...code redacted for brevity...]
public void setCurrentFrame(Image image){
[...code redacted for brevity...]
}
}
public class HardDriveNuker {//从硬盘删除文件
public void deleteHardDriveNow(){//立即删除
[...code redacted for brevity...]
}
}
public class SmartHardDriveNuker extends HardDriveNuker{//实现人脸识别算法
public void activate(CameraFeed cameraFeed){调用deleteHardDriveNow
[...code redacted for brevity...]
}
}
👆上面设计不太好——人脸识别和删除文件在一个类里实现。你可以重构,但是不幸地是,代码被加密。
👆流程
现在不得不硬着头皮编写单元测试。
Spy
显然需要测试的是SmartHardDriveNuker
类的activate()
方法,activate()
中调用deleteHardDriveNow()
方法。每一次调用人脸识别方法就删除文件是不现实的,需要在不影响人脸识别算法正常运行的情况下mock
删除文件方法。Spock
支持Spy
——调用真实方法,除非手工指定返回值:
def "automatic deletion of hard disk when agents are here"() {
given: "a camera feed"
CameraFeed cameraFeed = new CameraFeed()
and: "the auto-nuker program"
SmartHardDriveNuker nuker = Spy(SmartHardDriveNuker)//Spy
nuker.deleteHardDriveNow() >> {println "Hard disk is cleared"}//mock不需要实际运行的方法
when:"agents are knocking the door"
cameraFeed.setCurrentFrame(ImageIO.read(getClass().getResourceAsStream( "agents.jpg")))
nuker.activate(cameraFeed);//人脸识别算法运行
then: "all files of hard drive should be deleted"
1 * nuker.deleteHardDriveNow()//验证文件删除被调用
}
👆activate()
执行人脸识别算法实际代码,而不执行删除文件的实际代码。
Spy意味着问题
只有在不能重构代码的情况下才使用Spy
,一旦使用Spy
就意味着代码违反OOP
原则。
使用Mock/Stub代替Spy
假设现在可以重构代码,那么应该是这个样子的:
public class SmartHardDriveNuker{//不使用继承
private final HardDriveNuker hardDriveNuker;//通过组合方式重用
public SmartHardDriveNuker(final HardDriveNuker hardDriveNuker) {//构造方法注入
this.hardDriveNuker = hardDriveNuker;
}
public void activate(CameraFeed cameraFeed) {
[...code redacted for brevity..]
hardDriveNuker.deleteHardDriveNow();//调用外部依赖方法
[...code redacted for brevity..]
}
}
def "automatic deletion of hard disk when agents are here"() {
given: "a camera feed and a fake nuker"
CameraFeed cameraFeed = new CameraFeed()
HardDriveNuker nuker = Mock(HardDriveNuker)//Mock代替Spy
and: "the auto-nuker program"
SmartHardDriveNuker smartNuker = new SmartHardDriveNuker(nuker)//诸如
when:"agents are knocking the door"
cameraFeed.setCurrentFrame(ImageIO.read(getClass().getResourceAsStream("agents.jpg")))
smartNuker.activate(cameraFeed);//调用算法
then: "all files of hard drive should be deleted"
1 * nuker.deleteHardDriveNow()//验证
}
👆可以看到使用Mock
替代了Spy
,源码也更符合OOP
原则。