Java是Java的更好的替代品,但是大量使用静态方法很难用Mockito进行模拟。从我自己的类中,我注入了一个FileSystem
实例,在单元测试期间用模拟替换它。
但是,为了实现这一点,我需要模拟许多方法(并且还需要创建大量的模拟)。这种情况在我的测试类中多次发生。因此,我开始考虑设置一个简单的API来注册路径和声明相关行为。
例如,我需要检查流打开时的错误处理。主修班:
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 */
}
}
}
考试班:
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
/* ... */
}
}
我有许多这类的类/测试,而模拟设置可能更棘手,因为静态方法可以通过路径API调用3-6个非静态方法。为了避免大多数冗余代码,我进行了重构测试,但是随着Path API使用量的增加,我的简单API往往非常有限。所以现在又是重构的时候了。
然而,我所想到的逻辑似乎很糟糕,需要大量代码才能用于基本用法。我想要简化API模拟(不管是Java路径API还是非Java路径API)的方式都是基于以下原则:
为了实现第三步,我考虑创建一个Answer
,它查找已实现的方法并返回到默认答案。然后在模拟创建时传递此Answer
的一个实例。
是否有直接从Mockito或其他方法来解决这个问题的方法?
发布于 2015-08-28 14:45:50
你的问题是你违反了Single Responsibility Principle。
你有两个顾虑:
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 */
}
}
}
class StreamProcessor {
public StreamProcessor() {
// maybe set dependencies, depending on the need of your app
}
public void process(InputStream in) throws IOException {
/* file content handling */
}
}
现在我们把责任分成两部分。执行您想要测试的所有业务逻辑的类(从InputStream
)只需要一个输入流。事实上,我甚至不会嘲笑这一点,因为这只是数据。您可以任意方式加载InputStream
,例如,正如您在问题中提到的那样使用ByteArrayInputStream
。Java在您的StreamProcessor
测试中不需要任何代码。
此外,如果您正在以一种常见的方式访问文件,则只需进行一次测试就可以确保该行为有效。您还可以使StreamProcessor
成为一个接口,然后在代码库的不同部分为不同类型的文件执行不同的任务,同时将不同的StreamProcessor
传递到文件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 */
}
}
新代码:
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 */
}
}
public class StreamProcessor {
public void process(InputStream in) throws IOException {
/* file content handling */
/* just cut-paste the other code */
}
}
这是我上面描述的最不具侵略性的方法。我最初描述的方式更好,但很明显,这是一个更复杂的重构。这种方式几乎不涉及任何其他代码更改,但将允许您编写测试。
https://stackoverflow.com/questions/32271703
复制相似问题