Log*_*Mzz 5 java unit-testing path mocking mockito
Java Path API是Java File API的更好替代品,但静态方法的大量使用使得很难使用Mockito进行模拟.从我自己的类中,我注入一个FileSystem
实例,在单元测试期间我用模拟替换它.
但是,我需要模拟很多方法(并且还创建了很多模拟)来实现这一点.在我的测试课程中,这种情况反复发生了很多次.所以我开始考虑设置一个简单的API来注册Path-s并声明相关的行为.
例如,我需要检查流开放时的错误处理.主要课程:
class MyClass {
private FileSystem fileSystem;
public MyClass(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
/* file content handling */
} catch (IOException e) {
/* business error management */
}
}
}
Run Code Online (Sandbox Code Playgroud)
测试类:
class MyClassTest {
@Test
public void operation_encounterIOException() {
//Arrange
MyClass instance = new MyClass(fileSystem);
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
Path path = mock(Path.class);
doReturn(path).when(fileSystem).getPath("/dir/file.txt");
doReturn(fileSystemProvider).when(path).provider();
doThrow(new IOException("fileOperation_checkError")).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());
//Act
instance.operation();
//Assert
/* ... */
}
@Test
public void operation_normalBehaviour() {
//Arrange
MyClass instance = new MyClass(fileSystem);
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
Path path = mock(Path.class);
doReturn(path).when(fileSystem).getPath("/dir/file.txt");
doReturn(fileSystemProvider).when(path).provider();
ByteArrayInputStream in = new ByteArrayInputStream(/* arranged content */);
doReturn(in).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());
//Act
instance.operation();
//Assert
/* ... */
}
}
Run Code Online (Sandbox Code Playgroud)
我有很多这种类/测试,并且模拟设置可能更棘手,因为静态方法可能会通过Path API调用3-6个非静态方法.我已经重构测试以避免大多数冗余代码,但随着我的Path API使用量的增长,我的简单API往往非常有限.所以再次重构是时候了.
但是,我正在考虑的逻辑似乎很难看,并且需要很多代码才能用于基本用法.我希望简化API模拟(无论是否是Java Path API)的方式基于以下原则:
为了实现第三步,我考虑创建一个Answer
查找已实现的方法并回退到默认答案.然后Answer
在模拟创建时传递一个这样的实例.
是否有现成的方法直接从Mockito或其他方式来解决这个问题?
您的问题是您违反了单一责任原则.
你有两个问题:
InputStream
你试图用一种方法做这两种工作,这迫使你做大量的额外工作.相反,将工作分成两个不同的类.例如,如果你的代码是这样构造的:
class MyClass {
private FileSystem fileSystem;
private final StreamProcessor processor;
public MyClass(FileSystem fileSystem, StreamProcessor processor) {
this.fileSystem = fileSystem;
this.processor = processor;
}
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
processor.process(in);
} catch (IOException e) {
/* business error management */
}
}
}
Run Code Online (Sandbox Code Playgroud)
class StreamProcessor {
public StreamProcessor() {
// maybe set dependencies, depending on the need of your app
}
public void process(InputStream in) throws IOException {
/* file content handling */
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们把责任分为两个地方.执行所有要测试的业务逻辑工作的类InputStream
,只需要一个输入流.事实上,我甚至不会嘲笑它,因为它只是数据.您可以以InputStream
任何方式加载,例如使用ByteArrayInputStream
您在问题中提到的方式.您的StreamProcessor
测试中不需要任何Java Path API代码.
此外,如果您以通用方式访问文件,则只需要进行一次测试即可确保该行为有效.您还可以创建StreamProcessor
一个接口,然后在代码库的不同部分为不同类型的文件执行不同的作业,同时将不同的StreamProcessor
s 传递到文件API中.
在评论中你说:
听起来不错,但我必须忍受大量遗留代码.我开始介绍单元测试,不想重构太多的"应用程序"代码.
最好的方法就是我上面所说的.但是,如果您想进行最少量的更改以添加测试,那么您应该执行以下操作:
旧代码:
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
/* file content handling */
} catch (IOException e) {
/* business error management */
}
}
Run Code Online (Sandbox Code Playgroud)
新代码:
public void operation() {
String filename = /* such way to retrieve filename, ie database access */
try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
new StreamProcessor().process(in);
} catch (IOException e) {
/* business error management */
}
}
Run Code Online (Sandbox Code Playgroud)
public class StreamProcessor {
public void process(InputStream in) throws IOException {
/* file content handling */
/* just cut-paste the other code */
}
}
Run Code Online (Sandbox Code Playgroud)
这是我进行上述描述的侵入性最小的方法.我描述的原始方式更好,但显然它是一个更复杂的重构.这种方式几乎不涉及其他代码更改,但允许您编写测试.
归档时间: |
|
查看次数: |
4743 次 |
最近记录: |