我有一个名为A
的类和方法method1()
:
Class A{
boolean method1(String path){
File procDir = new File(path);
if(!procDir.exists()){
return false`;
}
if(!procDir.canRead()){
return false;
}
}
}
考虑上面的代码,有人会建议,破解方法1和内部方法/变量(procDir.canRead()
)的方法。
第一个答案是:你不能单独使用Mockito。
你需要一个能够模拟对new
调用的模拟框架。例如PowerMock(ito)或JMockit。您可以在此处找到有关如何使用 PowerMock 执行此操作的文档。从这个意义上说,从技术上讲,这是一个已解决的问题 - 它只需要一些摆动就可以让它工作(如果你弄错了一个先决条件,它根本不起作用)。
但除此之外,其他答案都是正确的:你创建了难以测试的代码,现在你正在寻找一个创可贴来解决你不灵活的设计的后果。
因此,与其花费宝贵的时间在 PowerMock(ito) 上,不如遵循您已经得到的建议并修复测试问题的根本原因。例如,通过使用某种依赖关系注入向此代码提供 File 对象(而不是让此代码调用new
本身)。
这是一个设计问题,因为该方法与File
紧密耦合
显式依赖关系原则指出:
方法和类应显式要求(通常通过方法参数或构造函数参数)它们需要的任何协作对象才能正常运行。
考虑到这一点,请考虑重构方法以显式依赖于File
boolean method1(File procDir){
if(!procDir.exists()){
return false`;
}
if(!procDir.canRead()){
return false;
}
return true;
}
这也会将依赖项的创建反转为类/方法外部的委托。这也允许方法显式声明它实际需要的内容。
现在该方法已解耦,可以通过传递正确的文件或模拟来测试该方法。
您还可以考虑抽象File
public interface FileInfo {
boolean exists();
boolean canRead()
}
因为类应该依赖于抽象而不是具体化。
boolean method1(FileInfo procDir){
if(!procDir.exists()){
return false`;
}
if(!procDir.canRead()){
return false;
}
return true;
}
然后,FileInfo
的实现可以封装实际File
并公开所需的行为。
对于测试,现在应该更容易通过继承或模拟框架直接模拟/存根/伪造抽象。
FileInfo file = mock(FileInfo.class);
when(file.exists()).thenReturn(true);
when(file.exists()).thenReturn(true);
subject.method1(file);
您可以使用 JUnit 的 TemporaryFolder 规则(或者,如果使用 JUnit5,则使用其等效扩展名)为您的测试创建输入文件。
然后你会...
- 将此文件的路径传递到
method1()
- 测试快乐路径 - 将不存在的文件路径传递到
method1()
以测试!procDir.exists()
悲伤路径
测试完成后,JUnit将丢弃临时文件夹,从而坚持自包含的测试原则。
此方法允许您在没有任何模拟的情况下测试代码,同时仍然是自包含的单元测试。
或者,您可以将File procDir = new File(path);
调用隐藏在接口后面:
public interface FileCreator {
File create(String path);
}
通过一个简单的实现:
public class FileCreatorImpl implements FileCreator {
public File create(String path) {
return new File(path);
}
}
测试时,可以将Mock(FileCreator.class)
注入到包含method1()
的类的实例中,并按如下方式设置对该实例的期望:
String filePath = "...";
File file = Mockito.mock(File.class);
Mockito.when(fileCreator.create(filePath)).thenReturn(file);
// happy path
Mockito.when(file.exists()).thenReturn(true);
Mockito.when(file.canRead()).thenReturn(true);
assertThat(sut.method1(filePath), is(true));
// sad path 1
Mockito.when(file.exists()).thenReturn(false);
assertThat(sut.method1(filePath), is(false));
// sad path 2
Mockito.when(file.exists()).thenReturn(true);
Mockito.when(file.canRead()).thenReturn(false);
assertThat(sut.method1(filePath), is(false));