如何对文件访问进行单元测试 (Java)? [英] How to Unit Test File Access (Java)?

查看:49
本文介绍了如何对文件访问进行单元测试 (Java)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道一个好的单元测试不应该访问文件系统.所以我也知道,你可以使用 Mockito 和 PowerMock 来模拟 File 类.

但是下面的代码呢:

public ClassLoaderProductDataProvider(ClassLoader classLoader, String tocResourcePath, boolean checkTocModifications) {//...this.cl = 类加载器;tocUrl = cl.getResource(tocResourcePath);如果(tocUrl == null){throw new IllegalArgumentException("找不到目录文件" + tocResourcePath);}this.checkTocModifications = checkTocModifications;toc = loadToc();//...}私有 ReadonlyTableOfContents loadToc() {输入流是 = null;文档文档;尝试 {is = tocUrl.openStream();doc = getDocumentBuilder().parse(is);} 捕获(异常 e){throw new RuntimeException("从"加载目录时出错" + tocUrl.getFile(), e);} 最后 {如果(是!= null){尝试 {is.close();} catch (IOException e) {抛出新的运行时异常(e);}}}尝试 {元素 tocElement = doc.getDocumentElement();ReadonlyTableOfContents toc = new ReadonlyTableOfContents();toc.initFromXml(tocElement);返回目录;} 捕获(异常 e){throw new RuntimeException("从 xml 创建目录时出错.", e);}}

这个类用位于 tocResource 的文件内容初始化它的 toc 属性.

所以我想到的第一件事就是创建一个在构造函数中不调用 super 的子类,因此所有文件访问都没有完成.在我自己的构造函数中,然后我为应该从文件中读取的数据插入测试虚拟数据.然后我可以毫无问题地测试其余的课程.

但是,那么原始类的构造函数代码根本没有测试.如果出现错误怎么办?

解决方案

事情是这样的:通常,为了使单元测试正常工作,您需要为您的类提供接口而不是具体的类来允许您灵活地进行不同的测试.看看你的例子,在我看来,你应该提取将 Document 加载到其他类的责任......使用名为 DocumentSource 的接口,比如说.>

那么这里的代码根本不依赖于文件系统.它可能看起来像

public SomethingProductDataProvider(DocumentSource source, String tocDocumentName,布尔值 checkTocModifications) {this.source = 来源;this.tocDocumentName = tocDocumentName;this.checkTocModifications = checkTocModifications;this.toc = loadToc();}私有 ReadonlyTableOfContents loadToc() {文档 doc = source.getDocument(tocDocumentName);如果(文档 == 空){throw new IllegalArgumentException("找不到目录文件" +tocResourcePath);}尝试 {元素 tocElement = doc.getDocumentElement();ReadonlyTableOfContents toc = new ReadonlyTableOfContents();toc.initFromXml(tocElement);返回目录;} 捕获(异常 e){throw new RuntimeException("从 xml 创建目录时出错.", e);}}

或者,您可以让类直接在其构造函数中使用 Document 甚至 InputStream.当然,在某些时候,您必须拥有使用 ClassLoader 从资源加载 InputStream 的实际代码...只有这样做.然后很明显,您对那个类所做的任何测试都必须使用实际文件……但其他类的测试不受影响.

作为旁注,如果类在其构造函数中起作用(例如在这种情况下加载目录),则它对类的可测试性来说是一个坏兆头.可能有一种更好的方法来设计这里涉及的类,它消除了这种需要并且更易于测试,但很难确切地说这种设计是什么.

您还可以使用其他各种选项,包括使用 Guava 的 InputSupplier 接口与已经测试过的工厂方法相结合,例如 Resources.newInputStreamSupplier(URL) 用于获取 用于生产的 InputSupplier 实例.不过,关键是始终让您的类依赖于接口,以便您可以在测试中轻松使用替代实现.

I know that a good unit test should never access the file system. So I also know, that you can use Mockito and PowerMock for example to mock out the File class.

But what about the following code:

public ClassLoaderProductDataProvider(ClassLoader classLoader, String tocResourcePath, boolean checkTocModifications) {
    // ...
    this.cl = classLoader;
    tocUrl = cl.getResource(tocResourcePath);
    if (tocUrl == null) {
        throw new IllegalArgumentException("Can' find table of contents file " + tocResourcePath);
    }
    this.checkTocModifications = checkTocModifications;
    toc = loadToc();
    // ...
}

private ReadonlyTableOfContents loadToc() {
    InputStream is = null;
    Document doc;
    try {
        is = tocUrl.openStream();
        doc = getDocumentBuilder().parse(is);
    } catch (Exception e) {
        throw new RuntimeException("Error loading table of contents from " + tocUrl.getFile(), e);
    } finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    try {
        Element tocElement = doc.getDocumentElement();
        ReadonlyTableOfContents toc = new ReadonlyTableOfContents();
        toc.initFromXml(tocElement);
        return toc;
    } catch (Exception e) {
        throw new RuntimeException("Error creating toc from xml.", e);
    }
}

This class initializes it's toc attribute with the contents of the file located at tocResource.

So the first thing that comes to my mind for the test is to create a sub class which does not call super in the constructor so all the file access isn't done. In my own constructor then I insert test dummy data for the data which should have been read from the file. Then I can test the rest of the class without problem.

However, then the constructor code of the original class is not tested at all. What if there's an error?

解决方案

Here's the thing: typically, to make proper unit testing work, you need to provide your classes with interfaces rather than concrete classes to allow you flexibility in doing different things for testing. Looking at your example, it seems to me that you should extract the responsibility of loading a Document to some other class... with an interface called DocumentSource, say.

Then your code here wouldn't depend on the file system at all. It might look something like

public SomethingProductDataProvider(DocumentSource source, String tocDocumentName,
                                    boolean checkTocModifications) {
  this.source = source;
  this.tocDocumentName = tocDocumentName;
  this.checkTocModifications = checkTocModifications;
  this.toc = loadToc();
}

private ReadonlyTableOfContents loadToc() {
  Document doc = source.getDocument(tocDocumentName);
  if (doc == null) {
    throw new IllegalArgumentException("Can' find table of contents file " + 
        tocResourcePath);
  }

  try {
    Element tocElement = doc.getDocumentElement();
    ReadonlyTableOfContents toc = new ReadonlyTableOfContents();
    toc.initFromXml(tocElement);
    return toc;
  } catch (Exception e) {
    throw new RuntimeException("Error creating toc from xml.", e);
  }
}

Alternatively, you could have the class take a Document or even an InputStream directly in its constructor. Of course, at some point you have to have the actual code that loads the InputStream from the resource using the ClassLoader... but you can push that code into something simple that only does that. Then it's clear that any testing you do of that class must use an actual file... but the testing of other classes is not affected.

As a side note, it's a bad sign for the testability of a class if it does work (such as loading the table of contents in this case) in its constructor. There is probably a much better way of designing the classes involved here that eliminates the need for that and is more testable, but it's hard to say exactly what that design is given just this.

There are various other options for what you could do as well, including the use of something like Guava's InputSupplier interface combined with an already-tested factory method like Resources.newInputStreamSupplier(URL) for getting the InputSupplier instance for use in production. The key thing, though, is to always have your classes depend on interfaces so that you can make easy use of alternate implementations in testing.

这篇关于如何对文件访问进行单元测试 (Java)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆