如何编写单元测试来覆盖抛出IOException的情况?

时间:2011-07-15 00:23:53

标签: java unit-testing

我有以下课程:

    public class FileLoader {   

         private Map<Brand, String> termsOfUseText = new HashMap<Brand, String>();

         public void load() {
            for (Brand brand : Brand.values()) {
                readAndStoreTermsOfUseForBrand(brand);
            }
         }

         private void readAndStoreTermsOfUseForBrand(Brand brand) {
            String resourceName = "termsOfUse/" + brand.name().toLowerCase() + ".txt";          
            InputStream in = this.getClass().getClassLoader().getResourceAsStream(resourceName);            
            try {
                String content = IOUtils.toString(in);          
                termsOfUseText.put(brand, content);
            } catch (IOException e) {
                throw new IllegalStateException(String.format("Failed to find terms of use source file %s", resourceName),e);
            }
         }

        public String getTextForBrand(Brand brand) {
            return termsOfUseText.get(brand);
        }
    }

Brand是一个枚举,我需要所有有效的.txt文件都在类路径上。如果Brand enum包含所有有效的品牌,并且所有的.txt文件都存在,那么如何使IOException发生呢?

有关重构当前代码的建议,如果它更容易测试,欢迎提出!

5 个答案:

答案 0 :(得分:3)

我立即看到的三个选项:

  1. 使用PowerMock模拟IOUtils.toString()。我认为PowerMock是最后的手段。我宁愿将源代码重构为更适合测试的东西。
  2. 将IOUtils调用解压缩到受保护的方法。创建类的特定于测试的子类,该子类重写此方法并抛出IOException。
  3. 将InputStream创建提取到受保护的方法。创建一个特定于测试的子类来覆盖该方法并返回一个模拟InputStream。

答案 1 :(得分:1)

我建议您进行一些重构。您所有的方法都是 void ,这通常意味着它们不起作用。 例如,您可以提取此功能:

private String readTermsOfUseForBrand(InputStream termsOfUserIs) {
    try {
        String content = IOUtils.toString(in);
        return content;
    } catch (IOException e) {
        throw new IllegalStateException(String.format("Failed to find terms of use source file %s", resourceName), e);
    }
    return null;
}

这样我们就可以在测试中对String结果进行断言。 当然,这不是功能代码,因为它是从输入流中读取的。而且它是通过IOUtils.toString()方法实现的,该方法不容易被嘲笑(嗯,有PowerMock,但正如Ryan Stewart所说的,这是最后的手段)。

要测试IO异常,您可以创建一个失败的输入流(已通过JDK7测试):

public class FailingInputStream extends InputStream {
    @Override
    public int read() throws IOException {
        throw new IOException("Test generated exception");
    }
}

并像这样测试:

@Test
public void testReadTermsOfUseForBrand() {
    FileLoader instance = new FileLoader();
    String result = instance.readTermsOfUseForBrand(new FailingInputStream());
    assertNull(result);
}

答案 2 :(得分:0)

缺少文件会导致NullPointerException,因为getResourceAsStream将返回null,您将拥有in==null。在这种情况下,IOException实际上可能非常罕见。如果你看到它是至关重要的,那么我只能想到如果在测试范围内执行代码,则将此代码用于抛出它。但它真的那么重要吗?

答案 3 :(得分:0)

我会用模拟来完成这个。

示例(未经测试,只是为了给你一些想法):

<?php 
    $change_reason = isset($_REQUEST["change_reason"]) ? $_REQUEST["change_reason"] : null;
    if($change_reason){
        switch ($change_reason) {
            case 1:
                echo '<p>Getting apple, OK!</p>
<textarea name="apples" rows="2">So I am getting some apples</textarea>';
                break;
            case 2:
                echo '<p>Walking outside, OK!</p>
<textarea name="apples" rows="2">So I will walk outside now</textarea>';
                break;
        }
    }
?>

答案 4 :(得分:-1)

以下代码完全未经测试 要导致IOException,请使用:

FileInputStream in = this.getClass().getClassLoader().getResourceAsStream(resourceName);
in.mark(0);  
//read some data
in.reset(); //IOException

要测试IOException案例,请使用:

void test  
{  
    boolean success = false;  
    try  
    {  
      //code to force ioException  
    }  
    catch(IOException ioex)  
    {  
       success = true;  
    }  
    assertTrue(success);  
}

在JUnit4中

@Test(expected=IOException.class)  
void test  
{  
   //code to force ioException  
}

其他JUnit

void test  
{  
   try  
   {  
     //code to force IOException  
     fail("If this gets hit IO did not occur, fail test");  
   }
   catch(IOException ioex)  
   {
    //success!
    }  
}