如何测试这个文件写入,3行的功能



这是我在某个服务类中的方法。它是公开的,所以应该进行测试。我根本不知道该测试什么。我会模拟Writer和spyOn函数调用,但使用这种实现是不可能的(不是吗?)

我正在使用MockitoJUnit

目前,我只能使函数抛出并断言异常

有什么帮助吗?

@Override
public void initIndexFile(File emptyIndexFile) {
try {
Writer writer = new FileWriter(emptyIndexFile);
writer.write("[]");
writer.close();
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}

如果您觉得添加特殊内容是业务逻辑,因此是类的责任的话,那么创建FileWriter就不是(根据单一责任模式)。

因此,您应该使用一个FileWriterFactory,它被注入到测试中的中。然后,您可以对FileWriterFactory进行模拟,以返回Writer接口的模拟实现,在该接口上,您可以检查它是否获得了预期的String。

你的提示将更改为:

private final WriterFactory writerFactory;
public ClassUnderTest(@Inject WriterFactory writerFactory){
this.writerFactory = writerFactory;
}
@Override
public void initIndexFile(File emptyIndexFile) {
try {
Writer writer = writerFactory.create(emptyIndexFile);
writer.write("[]");
writer.close();
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}

以及你的测试:

class Test{
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 
@Mock
private FileWriterFactory fileWriterFactory;
private Writer fileWriter = spy(new StringWriter());
File anyValidFile = new File(".");
@Test
public void initIndexFile_validFile_addsEmptyraces(){
//arrange
doReturn(fileWriter).when(fileWriterFactory).create(any(File.class));
// act
new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
//assert
verify(fileWriterFactory)create(anyValidFile);
assertEquals("text written to File", "[]", fileWriter.toString());
verify(fileWriter).close();
}
}

此外,你可以很容易地检查你的CuT是否拦截IOException:

@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void initIndexFile_missingFile_IndexFileInitializationException(){
//arrange
doReturnThrow(new IOException("UnitTest")).when(fileWriterFactory).create(any(File.class));
//assert
exception.expect(IndexFileInitializationException.class);
exception.expectMessage("Error initialization index file "+anyValidFile.getPath());
// act
new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
}

不错!一个只测试3行代码的工厂Nicolas Filotto

这是一个很好的观点。

问题是:该类中是否有任何方法直接与File对象交互,并需要在之后创建FileWriter?

如果答案是"否"(很可能是这样),遵循KISS原则,您应该直接注入Writer对象而不是工厂,并使用不带File参数的方法。

private final Writer writer;
public ClassUnderTest(@Inject Writer writer){
this.writer = writer;
}
@Override
public void initIndexFile() {
try {
writer.write("[]");
writer.close();
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}

修改测试:

class Test{       
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 
@Rule public ExpectedException exception = ExpectedException.none();
@Mock
private FileWriterFactory fileWriterFactory;
@Mock
private Writer failingFileWriter;
private Writer validFileWriter = spy(new StringWriter());
File anyValidFile = new File(".");
@Test
public void initIndexFile_validFile_addsEmptyraces(){
//arrange         
// act
new ClassUnderTest(validFileWriter).initIndexFile();
//assert
verify(fileWriterFactory)create(anyValidFile);
assertEquals("text written to File", "[]", fileWriter.toString());
verify(fileWriter).close();
}
@Test
public void initIndexFile_missingFile_IndexFileInitializationException(){
//arrange
doReturnThrow(new IOException("UnitTest")).when(failingFileWriter).write(anyString());
//assert
exception.expect(IndexFileInitializationException.class);
exception.expectMessage("Error initialization index file "+anyValidFile.getPath());
// act
new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
}
}

要测试您的方法是否可以通过发送正确的命令与编写器正确交互,您的pogram必须公开某种"seam",以便您的测试可以配置模拟FileWriter。我不熟悉mockito,但一种方法是将FileWriter实例化封装在一个方法后面,然后您的测试可以覆盖该方法以返回模拟FileWriter

假设File是一个接口:

public Writer getFileWriter(File emptyIndexFile) {
return new FileWriter(emptyIndexFile);
}

这可以让你覆盖上面的测试方法,并返回一个假的Writer

@Override
public Writer getFileWriter(File emptyIndexFile) {
return mockFileWriterInstance;
}

然后,您的测试可以进行练习initIndexFile,并对操作进行断言。使用模拟文件编写器抛出IOException应该很简单,这样您就可以练习错误处理逻辑。

您只需在测试中为方法提供一个临时文件,然后检查它是否包含预期的[],然后再次删除该文件。

类似于:

public class FileWritingTest {
// File to provide to the method initIndexFile
private File file;
/* This is executed before the test */
@Before
public void init() throws IOException {
// Create a temporary file
this.file = File.createTempFile("FileWritingTest", "tmp");
// Indicates that it should be removed on exit
file.deleteOnExit();
}
/* This is executed after the test */
@After
public void clean() throws IOException {
// Delete the file once test over
file.delete();
}
@Test
public void testInitIndexFile() throws IOException {
FileWriting fw = new FileWriting();
// Call the method
fw.initIndexFile(this.file);
// Check that the content is [] as expected
Assert.assertEquals("[]", new String(Files.readAllBytes(file.toPath()))); 
}
}

NB 1:我依赖new String(byte[]),这意味着我像您在当前代码中那样依赖默认的字符编码,但这不是一个好的做法,我们应该显式设置字符编码以避免依赖平台。

NB 2:假设您使用的是java7或更高版本,您应该考虑使用try-with-resources语句来正确关闭您的编写器,那么您的代码将是:

public void initIndexFile(File emptyIndexFile) {
try (Writer writer = new FileWriter(emptyIndexFile)) {
writer.write("[]");
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}

模拟依赖项是可能的,也是自然的,但模拟在方法体中声明的对象是不自然的,也是不棘手的。

我设想3种解决方案:

1) 为什么你不能简单地断言文件是用期望的字符编写的,而不是嘲笑?

它避免了技巧,但如果您经常执行此任务并且希望对其进行单元测试,则它可能是多余的并且速度较慢

2) 将局部变量作为一个实例字段来模拟它。这似乎真的不是一个干净的解决方案。如果同一类中有多个方法进行这种处理,则可能会重用同一个编写器或具有多个编写器字段。在这两种情况下,你都可能有副作用。

3) 如果您执行了许多写操作,并且希望真正隔离对编写器的调用,那么您就有了一个解决方案:重新设计代码,使其具有可测试的类。

您可以提取一个依赖项来执行写入程序处理。该类可以为方法提供执行指令所需的参数。我们可以称之为:WriteService

public class WriteService {
...
public void writeAndClose(Writer writer, String message){
try {   
writer.write(message);
writer.close();
} 
catch (IOException e) {
throw new IndexFileInitializationException("Error initialization index  file " + emptyIndexFile.getPath());
}
}
}

此类是可测试的,因为编写器依赖项是一个参数。

你这样称呼新服务:

public class YourAppClass{
private WriteService writeService;
public YourAppClass(WriteService writeService){
this.writeService=writeService;
}
@Override
public void initIndexFile(File emptyIndexFile) {
Writer writer = new FileWriter(emptyIndexFile);
writeService.writeAndClose(writer,"[]");
}
}

现在initIndexFile()也可以通过嘲笑WriteService进行测试。您可以使用正确的参数检查是否在writeService上调用了tat writeAndClose()。

就我个人而言,我会使用第一种解决方案或第三种解决方案

相关内容

  • 没有找到相关文章

最新更新