清单 2 中的测试有其他一些重大的缺陷 —— 而不仅仅是硬编码 String 比较那么简单。首先,测试并不真正可读。第二,它惊人的脆弱;一旦 XML 文档的格式改变(包括添加空格),与其尝试修复 String 本身,还不如粘贴进一个新的文档副本。后,测试的本性会迫使您必须应付 Date 方面,虽然您并不想如此。
若想确保文档中第二个 Class 元素的 name 值是 com.acme.web.Account 又该如何呢?当然,您可以使用常规表达式或 String 搜索,但所需的工作量太大。这样看来,通过一个解析框架来操纵此 DOM 不是更有意义么?
XMLUnit 能否用于 TestNG?
XMLUnit 是一个 JUnit 扩展,但这并不意味着不能在 TestNG 使用它。只要它具有 API 而且此 API 支持委托同时不基于修饰器,那么您可以将几乎任何框架整合进 TestNG。
用 XMLUnit 进行测试
当您感觉自己为完成一项任务而努力过了头,您可以想想解决此问题是否还有更容易的捷径可寻。如果所要解决的问题涉及的是编程式地验证 XML 文档,那么所应想到的解决方案是 XMLUnit。
XMLUnit 是一种 JUnit 扩展框架,有助于开发人员测试 XML 文档。实际上,XMLUnit 是一种真正的 XML 测试的“多面手”:可以使用它来验证 XML 文档的结构、内容甚至该文档的指定部分。
简单的做法是使用 XMLUnit 在逻辑上对比运行时 XML 文档和预定义的有效控制文件。本质上讲,这是一种差异测试:假定一个 XML 文档是正确的,那么此应用程序在运行时是否会生成同样的东西?它是相对简单的一种测试,但也可以使用它来验证 XML 文档的结构和内容。也可以通过 XPath 的一点帮助来验证特定内容。
委托而非继承
首要原则是尽量避免测试用例继承。许多 JUnit 扩展框架,包括 XMLUnit,都提供可以通过继承得到的专门的测试用例来协助测试某一特定的架构。从框架继承来的测试用例都缺乏灵活性,这是 Java 平台的单一继承的范型所致。更多的时候,这些相同的 JUnit 扩展框架提供一个委托 API,此 API 可以更易于组合不同的框架,而无需采用严格的继承结构。
验证内容
可以通过委托或继承的方式使用 XMLUnit。作为佳策略,我建议避免测试用例继承。另一方面,从 XMLUnit 的 XMLTestCase 继承确实可以提供一些方便的声明方法(这些方法不是静态 的,因而也不能像 JUnit的 TestCase 声明一样被静态引用)。
不管您如何选择使用 XMLUnit,都必须实例化 XMLUnit 的解析器。您可以通过 System.setProperty 调用实例化它们,也可以通过 XMLUnit 核心类上的一些方便的 static 方法对它们进行实例化。
一旦用所需要的不同的解析器实例化 XMLUnit 之后,可以使用 Diff 类,这是从逻辑上对比两个 XML 文档所需的中心机制。在清单 3 中,我利用 XMLUnit 对 >testToXML test 做了一些改进:
清单 3. 改进后的 testToXML 测试
public class XMLReportTest extends TestCase {
protected void setUp() throws Exception {
XMLUnit.setControlParser(
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
XMLUnit.setTestParser(
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
XMLUnit.setSAXParserFactory(
"org.apache.xerces.jaxp.SAXParserFactoryImpl");
XMLUnit.setIgnoreWhitespace(true);
}
private Filter[] getFilters(){
Filter[] fltrs = new Filter[2];
fltrs[0] = new RegexPackageFilter("java|org");
fltrs[1] = new SimplePackageFilter("net.");
return fltrs;
}
private Dependency[] getDependencies(){
Dependency[] deps = new Dependency[2];
deps[0] = new Dependency("com.acme.resource.Configuration");
deps[1] = new Dependency("com.acme.xml.Document");
return deps;
}
public void testToXML() {
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(1165203021718L),
this.getFilters());
report.addTargetAndDependencies(
"com.acme.web.Widget", this.getDependencies());
report.addTargetAndDependencies(
"com.acme.web.Account", this.getDependencies());
Diff diff = new Diff(new FileReader(
new File("./test/conf/report-control.xml")),
new StringReader(report.toXML()));
assertTrue("XML was not identical", diff.identical());
}
}