在软件开发周期中,有时需要验证XML文档的结构或内容。 无论您要构建哪种类型的应用程序,测试XML文档都会带来一些挑战,尤其是在没有工具来简化该过程的情况下。
本月,我将首先向您展示为什么您不想使用String
比较来验证XML文档的结构和内容。 然后,我将介绍XMLUnit,这是由Java开发人员创建并为Java开发人员使用的XML验证工具,并向您展示如何使用它来验证XML文档。
首先,让我们假设您已经构建了一个应用程序,该应用程序输出代表对象相关性报告的XML文档。 对于给定的类集合和相应的过滤器,将生成一个报告,该报告输出一个类及其类相关性(请考虑导入)。
清单1显示了给定类com.acme.web.Widget
和com.acme.web.Account
类的列表的报告,其中设置了过滤器以忽略外部类,例如java.lang.String
:
<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显然是由应用程序生成的; 因此,第一级测试是验证应用程序实际上可以生成文档。 验证之后,您将至少要测试特定文档的三个方面:
您可以使用String
比较仅通过JUnit处理前两个方面,如清单2所示:
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
继承确实提供了一些方便的断言方法(它们不是static
,因此不能像JUnit的TestCase
断言一样被静态引用)。
无论选择如何使用XMLUnit,都必须初始化XMLUnit的解析器。 您可以通过System.setProperty
调用或通过XMLUnit
核心类上的一些方便的static
方法来初始化它们。
使用各种必需的解析器正确初始化XMLUnit之后,就可以使用Diff
类,这是用于逻辑比较两个XML文档的中心机制。 在清单3中,我使用了一些XMLUnit改进了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
,这是一个救命稻草,相信我! 否则,当两个文档由于空白不一致而不同时,您会发现很多失败!
Diff
类支持两种类型的比较: identical
和similar
。 如果两个比较的文档在结构和值上完全相同(如果设置了该标志,则忽略空白),则它们被视为相同 ; 如果两个文档相同,则它们也将相似 。 相反,不一定是正确的。
例如,清单4显示了一个简单的XML代码段,该代码段在逻辑上类似于清单5中的XML。 但是,它们并不相同:
<account>
<id>3A-00</id>
<name>acme</name>
</account>
清单5中的XML代码片段与清单4中的逻辑文档相同。但是,XMLUnit认为它们不是相同的,因为name
和id
元素已交换。
<account>
<name>acme</name>
<id>3A-00</id>
</account>
因此,我可以编写一个测试用例来验证XMLUnit的行为,如清单6所示:
public void testIdenticalAndSimilar() throws Exception {
String controlXML = "<account><id>3A-00</id><name>acme</name></account>";
String testXML = "<account><name>acme</name><id>3A-00</id></account>";
Diff diff = new Diff(controlXML, testXML);
assertTrue(diff.similar());
assertFalse(diff.identical());
}
相似和相同的XML文档之间的区别是细微的。 但是,验证两者的能力可能会非常有帮助,例如在测试情况下,文件是由不同的应用程序或客户端生成的。
除了验证内容之外,您有时还需要验证XML文档的结构。 在这种情况下,单个元素和属性的值无关紧要-这是您所关注的结构。
幸运的是,通过有效地忽略元素文本值和属性值,我可以重用清单3中定义的测试用例来验证文档的结构。 我通过在Diff
类上调用overrideDifferenceListener()
并将其提供给IgnoreTextAndAttributeValuesDifferenceListener
,该对象由XMLUnit提供。 修改后的测试如清单7所示:
public void testToXMLFormatOnly() throws Exception{
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(), 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()));
diff.overrideDifferenceListener(
new IgnoreTextAndAttributeValuesDifferenceListener());
assertTrue("XML was not similar", diff.similar());
}
当然,DTD和XML模式有助于XML结构验证。 但是,有时文档没有引用它们-在这种情况下,结构验证可能会有所帮助。 同样,如果您需要忽略特定的值(例如Date
),则可以实现DifferenceListener
接口(如IgnoreTextAndAttributeValuesDifferenceListener
那样)并提供自定义实现。
为了完成XML测试帽技巧,XMLUnit有助于使用XPath验证XML文档的特定部分。
例如,使用清单1中相同的文档格式,我想验证我的应用程序生成的第一个Class
元素的name
属性值是com.acme.web.Widget
。 为此,我必须创建一个XPath表达式以导航到确切的位置。 此外,XMLUnit的XMLTestCase
提供了一个方便的assertXpathExists()
方法,这意味着我现在必须扩展XMLTestCase
。
public void testToXMLFormatOnly() throws Exception{
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(), this.getFilters());
report.addTargetAndDependencies(
"com.acme.web.Widget", this.getDependencies());
report.addTargetAndDependencies(
"com.acme.web.Account", this.getDependencies());
assertXpathExists("//Class[1][@name='com.acme.web.Widget']",
report.toXML());
}
如清单8所示,XMLUnit与XPath协作,提供了一种方便的机制来验证XML文档的精确方面,而不是进行大的差异测试。 请记住,要利用XMLUnit中的XPath,您的测试用例必须扩展XMLTestCase
。 熟悉XPath也有帮助!
XMLUnit是一个基于Java的开源工具,与使用String
比较可以做的任何事情相比,它使测试XML文档变得更加容易和灵活。 使用XMLUnit进行差异测试的唯一可能缺点是,测试将依赖文件系统来加载控制文档。 在编写测试时,请考虑这种增加的依赖性。
尽管XMLUnit已有一段时间没有发布任何新更新,但其当前的功能集合足够强大,足以为测试带来巨大的成功-在这种情况下,它基本上是免费的!
翻译自: https://www.ibm.com/developerworks/java/library/j-cq121906/index.html