我知道一个好的单元测试永远不应该访问文件系统。所以我也知道,你可以使用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,因此所有文件访问都没有完成。在我自己的构造函数中,然后我插入应该从文件中读取的数据的测试虚拟数据。然后我可以毫无问题地测试其余的课程。
然而,原始类的构造函数代码根本没有测试过。如果出现错误怎么办?
答案 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的单元测试艺术将解释当涉及外部依赖时,单元测试是不合适的。这是出于本问题其他部分所述的原因:保持测试运行时速度快,使测试可在开发人员环境中重复,消除错误的测试失败等等。
从文件系统读取,即使这是一段代码的不可或缺的工作,也就是这样的外部依赖。所以在我看来,你试图强迫这种情况是可以单元测试的,而实际上并非如此。您应该使用模拟库和良好的解耦设计进行单元测试 - 并使用其他测试工具(如手动或自动集成测试)来测试外部依赖项。