【问题标题】:How to unit test and mock a method that takes a file as a parameter如何对将文件作为参数的方法进行单元测试和模拟
【发布时间】:2015-11-16 20:04:00
【问题描述】:

我有一个创建 ArrayList 的类 CollectionObject。

public class CollectionObject {

    private List<String> collectionObject;

    public CollectionObject() {
        collectionObject = new ArrayList<String>();
    }

    public List<String> getCollectionObject() {
        return collectionObject;
    }

    public void add(final String stringToWrite) throws VerifyException {
        collectionObject.add(stringToWrite);
    }
}

还有一个类接受 CollectionObject 类并使用它将文件的内容写入 CollectionObject 类。

public class ReaderFileWriterObjectService {

    private BufferedReader bufferedReader;
    private CollectionObject collectionObject;
    private String line;

    public CollectionObject getCollectionObjectAfterWritingFromAFile(final File file)
            throws VerifyException, IOException {
        collectionObject = new CollectionObject();
        bufferedReader = new BufferedReader(new FileReader(file));
        while ((line = bufferedReader.readLine()) != null) {
            collectionObject.add(line);
        }
        bufferedReader.close();
        return collectionObject;
    }

如何测试和模拟ReaderFileWriterObjectService类的方法?

【问题讨论】:

  • 你不能。 File 与 JDK 绑定太多,因此测试它非常困难。但是,如果您使用 JSR 203,情况会大不相同...

标签: java unit-testing junit mockito


【解决方案1】:

让我补充一下@LouisWasserman's answer

您无法测试依赖于java.io.File 的API;此类无法进行可靠的单元测试(即使在 JDK 级别它甚至不是 final)。

但 Java 7 中出现的新文件系统 API 并非如此。

也称为 JSR 203,此 API 为任何提供“文件系统对象”的存储介质提供统一的 API。

短篇小说:

  • “文件系统对象”通过此 API 中的路径实现;
  • 任何实现 JSR 203 的 JDK(即任何 Java 7+ 版本)都支持此 API;
  • 要从默认 FileSystem 的资源中获取路径,可以使用Paths.get()
  • 但您不限于此。

简而言之,在您的 API 测试用例中,您应该使用Path,而不是File。如果您想测试与某些文件系统资源相关的任何内容,请使用 JDK 的 Files 类来测试 Path 实例。

可以在您的基于磁盘的主文件系统中创建FileSystems。建议:使用this

【讨论】:

  • 使用Path 也有其他优点,它可以让您更清晰地分离关注点,并且您需要摆弄(或使用第 3 方库)File 的许多功能开箱即用。
  • @biziclop 不确定您的意思,但我确实使用此 API 在本地磁盘以外的其他东西上实现 FileSystems(例如 here),因此实现此 API 的复杂性并不也不要逃避我...
【解决方案2】:

我也在做同样的事情,下面的想法是有效的, 我希望这对你也有用,

 @InjectMocks
 private CollectionObject collectionObject;

@Test
public void getCollectionObjectAfterWritingFromAFile() throws Exception {
    CollectionObject  expectedObject =new CollectionObject();
    List<String> expectedList=new ArrayList<String>();
    expectedList.add("100");

    CollectionObject  resultObject =new CollectionObject();

    BufferedReader reader=new BufferedReader(new StringReader("100"));
    PowerMockito.mock(BufferedReader.class);
    PowerMockito.mock(FileReader.class);
    PowerMockito.whenNew(FileReader.class).withArguments("test10.csv").thenReturn(null);
    PowerMockito.whenNew(BufferedReader.class).withArguments(null).thenReturn(reader);
    resultObject=collectionObject.getCollectionObjectAfterWritingFromAFile( "test10.csv");
    assertEquals(expectedObject ,resultObject );
}

【讨论】:

    【解决方案3】:

    您可以使用 JUnit 的 TemporaryFolder 创建文件并将内容从资源复制到它。

    public YourText {
      @Rule
      public TemporaryFolder folder = new TemporaryFolder();
    
      @Test
      public void checkSomething() throws Exception {
        InputStream resource = getClass().getResourceAsStream("/your/resource");
        File file = folder.newFile();
        Files.copy(resource, file);
        ReaderFileWriterObjectService service = ...
        CollectionObject collection = service
            .getCollectionObjectAfterWritingFromAFile(file);
        ...
      }
    

    【讨论】:

      【解决方案4】:

      你不能。你很不走运。更好的设计将接受 Java 7 java.nio.file.FileSystemPath,它们可以被替换为测试实现,例如https://github.com/google/jimfs.

      【讨论】:

      • 好吧,jimfs 并不是真正用于单元测试的。 memoryfilesystem 在这里更安全
      • 差不多,是的。上次我检查时,jimfs 甚至没有正确实现 POSIX 权限,因为...
      • @fge 从问题的角度来看,关键是File 不可能,只能使用其中一种替代方法。选择哪种实现方式也很有用,但只是次要的。
      • @fge 我的措辞可能有误。我的意思是,实际选择的 FileSystem 实现与 JSR 203 应该是解决方案的基础这一事实相比,相关性较低(并且可能随时间变化)。
      【解决方案5】:

      好的,首先让我们考虑一下你想测试什么?如果是单元测试,那么您不想测试与文件系统通信之类的集成,您必须测试自己的逻辑,您的逻辑类似于: 1) 使用文件系统集成从文件中读取下一行 2)将此行添加到我的对象中

      第二步你不应该测试,因为这个方法太容易破解了。您无法测试的第一步是因为它执行集成调用。所以我不认为在这里你需要一个单元测试

      但是如果你的逻辑会更复杂,那么你可以引入接口包装器并在你的测试中模拟它:

      public interface FileWrapper{
        public String readLine();
        public void close();
      }
      public class FileWrapperImpl implements FileWrapper{
        private File file;
        private BufferedReader reader;
      
        public FileWrapperImpl (File file){
           this.file = file;
           this.reader= ...
        }
      
        public String readLine(){
          return reader.nextLine();
        }
      }
      

      然后是你的 ReaderFileWriterObjectService:

       public CollectionObject getCollectionObjectAfterWritingFromAFile(FileWrapper wrapper)
              CollectionObject  collectionObject = new CollectionObject();
              while ((line = wrapper.readLine()) != null) {
                  collectionObject.add(line);
              }
              wrapper.close();
              return collectionObject;
          }
      

      现在您可以轻松地模拟 FileWrapper 进行测试并将其传递给您的服务

      【讨论】:

        【解决方案6】:

        我建议更改 API 以接受 Reader 或 BufferedReader - 可以模拟这些。用工厂隐藏对文件的依赖。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-06-10
          • 2012-06-29
          • 2015-08-20
          • 2019-08-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多