Java™ 开发人员一般都很善于解决问题,所以由 Java 开发人员提出更容易的方法用以验证 XML 文档是很自然的事。本月,Andrew 将向您介绍 XMLUnit,一个能满足您所有的 XML 验证需求的 JUnit 扩展框架。
在软件开发周期中,需要不时地验证 XML 文档的结构或内容。不管构建的是何种应用程序,测试 XML 文档都具有一定的挑战性,尤其是在没有相关工具的情况下就更是如此。
本月,我将首先向您说明为何不能 使用 String
比较来验证 XML 文档的结构和内容。之后,我会介绍 XMLUnit,一个由 Java 开发人员创建并可服务于 Java 开发人员的 XML 验证工具,向您展示如何使用它来验证 XML 文档。
首先,假设您已经构建了一个应用程序,该应用程序可以输出代表对象依赖性报告的 XML 文档。对于给定的类和对应的过滤器的集合,会生成一个报告来输出类和类的依赖项(想象一下导入)。
清单 1 显示了用于给定类列表(com.acme.web.Widget
和 com.acme.web.Account
)的报告,过滤器被设为忽略外部类,比如 java.lang.String
:
清单 1. 一个示例依赖性 XML 报告
<DependencyReport date="Sun Dec 03 22:30:21 EST 2006"> <FiltersApplied> <Filter pattern="java|org"/> <Filter pattern="net."/> </FiltersApplied> <Class name="com.acme.web.Widget"> <Dependency name="com.acme.resource.Configuration"/> <Dependency name="com.acme.xml.Document"/> </Class> <Class name="com.acme.web.Account"> <Dependency name="com.acme.resource.Configuration"/> <Dependency name="com.acme.xml.Document"/> </Class></DependencyReport> |
清单 1 很明显是由应用程序生成的;因而,第一层测试就是验证应用程序是否真能生成一个文档。一旦验证了这一点,就可以继续测试指定文档的其他三个方面:
- 结构
- 内容
- 指定内容
可以通过单独使用 JUnit 利用 String
比较处理上述前两个方面,如清单 2 所示:
清单2. 硬性验证 XML
public class XMLReportTest extends TestCase { 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() { Date now = new Date(); BatchDependencyXMLReport report = new BatchDependencyXMLReport(now, this.getFilters()); report.addTargetAndDependencies( "com.acme.web.Widget", this.getDependencies()); report.addTargetAndDependencies( "com.acme.web.Account", this.getDependencies()); String valid = "<DependencyReport date=\"" + now.toString() + "\">"+ "<FiltersApplied><Filter pattern=\"java|org\" /><Filter pattern=\"net.\" />"+ "</FiltersApplied><Class name=\"com.acme.web.Widget\">" + " <Dependency name=\"com.acme.resource.Configuration\" />"+ "<Dependency name=\"com.acme.xml.Document\" /></Class>"+ "<Class name=\"com.acme.web.Account\">"+ "<Dependency name=\"com.acme.resource.Configuration\" />"+ "<Dependency name=\"com.acme.xml.Document\" />"+ "</Class></DependencyReport>"; assertEquals("report didn't match xml", valid, report.toXML()); }} |
清单 2 中的测试有其他一些重大的缺陷 —— 而不仅仅是硬编码 String
比较那么简单。首先,测试并不真正可读。第二,它惊人的脆弱;一旦 XML 文档的格式改变(包括添加空格),与其尝试修复 String
本身,还不如粘贴进一个新的文档副本。最后,测试的本性会迫使您必须应付 Date
方面,虽然您并不想如此。
若想确保文档中第二个 Class
元素的 name
值是 com.acme.web.Account
又该如何呢?当然,您可以使用常规表达式或 String
搜索,但所需的工作量太大。这样看来,通过一个解析框架来操纵此 DOM 不是更有意义么?
|
当您感觉自己为完成一项任务而努力过了头,您就可以想想解决此问题是否还有更容易的捷径可寻。如果所要解决的问题涉及的是编程式地验证 XML 文档,那么所应想到的解决方案就是 XMLUnit。
XMLUnit 是一种 JUnit 扩展框架,有助于开发人员测试 XML 文档。实际上,XMLUnit 是一种真正的 XML 测试的“多面手”:可以使用它来验证 XML 文档的结构、内容甚至该文档的指定部分。
最简单的做法是使用 XMLUnit 在逻辑上对比运行时 XML 文档和预定义的有效控制文件。本质上讲,这就是一种差异测试:假定一个 XML 文档是正确的,那么此应用程序在运行时是否会生成同样的东西?它是相对简单的一种测试,但也可以使用它来验证 XML 文档的结构和内容。也可以通过 XPath 的一点帮助来验证特定内容。
|
可以通过委托或继承的方式使用 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()); }} |
注意一下我是如何实例化 XMLUnit 的 setControlParser
、setTestParser
和 setSAXParserFactory
方法的。您可以为这些值使用任何兼容 JAXP 的解析器。还要注意我是用 true
调用 setIgnoreWhitespace
的 —— 这是一根救命稻草,相信我!否则,不一致的空白会导致很多故障。
文章来源于领测软件测试网 https://www.ltesting.net/