如何unit testing文件访问(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,因此所有文件访问都没有完成。 在我自己的构造函数中,然后我插入应该从文件中读取的数据的测试虚拟数据。 然后我可以毫无问题地测试其余的课程。

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

事情就是这样:通常,要进行适当的unit testing工作,您需要为类提供接口而不是具体类,以便您可以灵活地执行不同的测试。 看看你的例子,在我看来你应该把负责将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 。 当然,在某些时候你必须拥有使用ClassLoader从资源加载InputStream的实际代码……但是你可以将那些代码推送到只有这样做的简单代码中。 然后很明显,你对该类进行的任何测试都必须使用实际文件……但是其他类的测试不会受到影响。

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

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

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

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

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

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

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

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

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