如何单元测试文件访问(Java)?

时间:2011-02-22 17:41:02

标签: java unit-testing

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

但是下面的代码呢?

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);
    }
}

此类使用位于tocResource的文件内容初始化它的toc属性。

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

然而,原始类的构造函数代码根本没有测试过。如果出现错误怎么办?

5 个答案:

答案 0 :(得分:9)

这就是:通常,要进行适当的单元测试工作,您需要为类提供 interfaces 而不是具体的类,以便您可以灵活地执行不同的测试。看看你的例子,在我看来,你应该负责将Document加载到其他类......用一个名为DocumentSource的接口,比如说。

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

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);
  }
}

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

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

您还可以使用其他各种选项,包括使用Guava的InputSupplier界面和已经过测试的工厂方法(如Resources.newInputStreamSupplier(URL))来获取InputSupplier用于生产的实例。但关键是要始终让您的类依赖于接口,这样您就可以在测试中轻松使用替代实现。

答案 1 :(得分:4)

访问文件系统对于单元测试是完全可以接受的。事实上,将一整套文件用作被测系统的固定装置是很常见的。它使添加新测试变得容易,因为您不需要添加新代码,只需添加数据。

答案 2 :(得分:3)

你在哪里知道“好的”单元测试不应该访问文件系统?只要测试在多个环境中可重现,它就没有错。因此,在这种情况下,它意味着您在测试类路径上创建一个静态文件,并将该文件的路径传递给ClassLoaderProductDataProvider构造函数。不需要让它复杂化。

答案 3 :(得分:2)

您可以传入一个自定义的ClassLoader,它在调用时提供tocUrl的测试实例。但是,为什么要传递类加载器呢?如果您使用的只是tocUrl,只需将其传递给而不是ClassLoader和stub。它大大简化了事情。

public ClassLoaderProductDataProvider(ClassOfToUrl tocUrl, String tocResourcePath, boolean checkTocModifications) {
// ...
this.tocUrl = tocUrl;

这里的问题是你的构造函数正在工作以及设置状态。为了可测试,你真的想分开这两个任务。你可以看到为什么,你们都纠缠不清。

答案 4 :(得分:2)

单元测试只是测试工具包中的一个工具。但与所有工具一样,它具有预期目的和有限的适用范围。 Roy Osherove的单元测试艺术将解释当涉及外部依赖时,单元测试是不合适的。这是出于本问题其他部分所述的原因:保持测试运行时速度快,使测试可在开发人员环境中重复,消除错误的测试失败等等。

从文件系统读取,即使这是一段代码的不可或缺的工作,也就是这样的外部依赖。所以在我看来,你试图强迫这种情况是可以单元测试的,而实际上并非如此。您应该使用模拟库和良好的解耦设计进行单元测试 - 并使用其他测试工具(如手动或自动集成测试)来测试外部依赖项。