如何模拟FileInputStream和其他*流

问题描述:

我有类获取GenericFile作为输入参数读取数据并做一些额外的处理。我需要测试它:如何模拟FileInputStream和其他*流

public class RealCardParser { 

    public static final Logger l = LoggerFactory.getLogger(RealCardParser.class); 

    @Handler 
    public ArrayList<String> handle(GenericFile genericFile) throws IOException { 
     ArrayList<String> strings = new ArrayList<String>(); 
     FileInputStream fstream = new FileInputStream((File) genericFile.getFile()); 
     DataInputStream in = new DataInputStream(fstream); 
     BufferedReader br = new BufferedReader(new InputStreamReader(in)); 
     String strLine = br.readLine();//skip header 
     while ((strLine = br.readLine()) != null) { 
      l.info("handling in parser: {}", strLine); 
      strings.add(strLine); 
     } 
     br.close(); 
     return strings; 
    } 
} 

问题出在新的FileInputStream。我可以嘲笑GenericFile,但它是无用的,因为FileInputStream检查文件是否存在。所以,现在我可以重写方法getBufferedReader和测试方法处理

public class RealCardParser { 

    public static final Logger l = LoggerFactory.getLogger(RealCardParser.class); 

    protected BufferedReader getBufferedReader(GenericFile genericFile) throws FileNotFoundException { 
     FileInputStream fstream = new FileInputStream((File) genericFile.getFile()); 
     DataInputStream in = new DataInputStream(fstream); 
     return new BufferedReader(new InputStreamReader(in)); 
    } 

    @Handler 
    public ArrayList<String> handle(GenericFile genericFile) throws IOException { 
     ArrayList<String> strings = new ArrayList<String>(); 
     BufferedReader br = getBufferedReader(genericFile); 
     String strLine = br.readLine();//skip header 
     while ((strLine = br.readLine()) != null) { 
      l.info("handling in parser: {}", strLine); 
      strings.add(strLine); 
     } 
     br.close(); 
     return strings; 
    } 
} 

:我改变了我的类,所以

@RunWith(MockitoJUnitRunner.class) 
public class RealCardParserTest { 

    RealCardParser parser; 

    @Mock 
    GenericFile genericFile; 

    @Mock 
    BufferedReader bufferedReader; 

    @Mock 
    File file; 

    @Before 
    public void setUp() throws Exception { 
     parser = new RealCardParser() { 
      @Override 
      public BufferedReader getBufferedReader(GenericFile genericFile) throws FileNotFoundException { 
       return bufferedReader; 
      } 
     }; 

     when(genericFile.getFile()).thenReturn(file); 
     when(bufferedReader.readLine()).thenReturn("header").thenReturn("1,2,3").thenReturn(null); 
    } 

    @Test 
    public void testParser() throws Exception { 
     parser.handle(genericFile); 
     //do some asserts 
    } 
} 

处理方法现在覆盖测试,但我仍然发现,导致方法getBufferedReader cobertura问题。 如何测试方法getBufferedReader或者可能有另一个解决方案的问题?

+0

无关你的问题下面的代码,但我认为'makeBufferedReader'可能是比'getBufferedReader'这种方法更好的名字?对于大多数Java开发人员来说,以'get'开头的名称强​​烈建议您只是返回现有的属性,而不是创造新的东西。 –

也许这是一个坏主意,但我的第一种方法是创建一个实际的测试文件,而不是模拟流对象。

有人可能会争辩说,这将测试GenericFile类而不是getBufferedReader方法。

也许一个可以接受的方式是通过模拟GenericFile返回一个实际存在的测试文件来测试getBufferedReader

+0

我想过但它不再是单元测试,而是一种集成测试 –

+0

我恐怕没有看到任何方式。也许有更多写作测试经验的人可以在这里帮忙。即使你在你的实现中抛弃了FileInputStream,就像@Jens所建议的那样,你仍然需要在某个时候用“真实的东西”进行测试。 – Fildor

+0

内部创建的'java.io'对象可以用PowerMock或JMockit来模拟,但这里最好的解决方案是这个答案建议使用一个实际的文件。只要'GenericFile'被嘲弄,它仍然是一个单元测试(虽然即使对于真实的对象也可以被使用)。然而,我在被测试的类中看到了多个质量问题(例如,返回'ArrayList'和发送无意义的调试信息等等)。 –

我会先将流的创建提取到依赖项中。所以你的RealCardParser获得一个StreamSource作为依赖。

现在你可以把你的APPART问题:

  1. 为您当前的测试提供了一个模拟(或在这种情况下,我宁愿一个假的)实现返回从String构造的流。

  2. 用真实文件测试实际的StreamSource,确保它返回正确的内容,而不是。

我知道这不是你想要的答案。

单元测试的想法是确保你的逻辑是正确的。单元测试可以捕获错误逻辑写入错误。如果一个方法不包含逻辑(即没有分支,循环或异常处理),那么对它进行单元测试是不经济的。因此,我的意思是单元测试需要花费金钱 - 编写它的时间,以及维护它的时间。大多数单元测试都是通过发现错误或者向我们保证在测试领域没有错误来支付我们的投资。

但是您的getBufferedReader方法的单元测试不会为您支付我们的投资。它具有有限的成本,但零利益,因为没有实际的逻辑可能出错。因此,你不应该写这样的单元测试。如果您的Cobertura设置或您的组织标准要求存在这样的单元测试,那么这些设置或标准是错误的,应该改变。否则,你的雇主的钱将花在具有无限成本:收益比的东西上。

我强烈建议您更改标准,以便仅为包含分支,循环或异常处理的方法编写单元测试。

+0

那么我理解你的文章,但我们的要求非常简单,非常合乎逻辑:所有类(除了像基本类或自动生成的对象之类的基本类)都必须覆盖大于80%的测试。由于该类足够小,因此避免测试方法initBufferedReader导致代码覆盖率仅为73%。谈到该方法initBufferedReader没有可能失败的业务逻辑 - 我不同意。如果文件不存在并且存在可能会抛出异常的类型转换,则可能会引发异常。 –

+1

当然,你应该测试处理你提到的那两个异常的逻辑。但是,该逻辑并不在您的'getBufferedReader'方法中 - 它以任何方式_calls_“getBufferedReader”。因此,对于那些逻辑的测试属于任何方法的测试。我相信接下来的唯一代码覆盖百分比是这些 - 您应该对代码中的逻辑有100%的覆盖率,对代码没有逻辑的覆盖率为0%。我认为有一个单一的百分比(无论是60%还是90%)作为组织标准是一个错误。 –

+0

我会upvote这个答案,如果它回答了这个问题。我真的很喜欢你所说的话,但它并没有真正解决问题(关于嘲笑FileInputStream),这就是为什么我无法提供它的原因。好消息,否则。 – searchengine27

您可以使用PowerMockRunner和PowerMockito模拟FileInputStream。见mocking-

@RunWith(PowerMockRunner.class) 
@PrepareForTest({ 
     FileInputStream.class 
}) 
public class A{ 

@Test 
     public void testFileInputStream() 
        throws Exception 
     { 
      final FileInputStream fileInputStreamMock = PowerMockito.mock(FileInputStream.class); 
      PowerMockito.whenNew(FileInputStream.class).withArguments(Matchers.anyString()) 
           .thenReturn(fileInputStreamMock); 
    //Call the actual method containing the new constructor of FileInputStream 

     } 
}