当前位置: 首页 > 工具软件 > XMLUnit > 使用案例 >

xmlunit_发现XMLUnit

党源
2023-12-01

在软件开发周期中,有时需要验证XML文档的结构或内容。 无论您要构建哪种类型的应用程序,测试XML文档都会带来一些挑战,尤其是在没有工具来简化该过程的情况下。

本月,我将首先向您展示为什么您不想使用String比较来验证XML文档的结构和内容。 然后,我将介绍XMLUnit,这是由Java开发人员创建并为Java开发人员使用的XML验证工具,并向您展示如何使用它来验证XML文档。

旧的String比较好

首先,让我们假设您已经构建了一个应用程序,该应用程序输出代表对象相关性报告的XML文档。 对于给定的类集合和相应的过滤器,将生成一个报告,该报告输出一个类及其类相关性(请考虑导入)。

清单1显示了给定类com.acme.web.Widgetcom.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显然是由应用程序生成的; 因此,第一级测试是验证应用程序实际上可以生成文档。 验证之后,您将至少要测试特定文档的三个方面:

  • 结构体
  • 内容
  • 具体内容

您可以使用String比较仅通过JUnit处理前两个方面,如清单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会更有意义吗?

使用XMLUnit进行测试

当您感觉到自己在努力工作时,通常可以认为其他人已经找到了解决问题的简便方法。 在以编程方式验证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测试

清单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的setControlParsersetTestParsersetSAXParserFactory方法。 您可以使用任何符合JAXP的解析器框架来获取这些值。 另请注意,我将true称为setIgnoreWhitespace ,这是一个救命稻草,相信我! 否则,当两个文档由于空白不一致而不同时,您会发现很多失败!

与Diff的比较

Diff类支持两种类型的比较: identicalsimilar 。 如果两个比较的文档在结构和值上完全相同(如果设置了该标志,则忽略空白),则它们被视为相同 ; 如果两个文档相同,则它们也将相似 。 相反,不一定是正确的。

例如,清单4显示了一个简单的XML代码段,该代码段在逻辑上类似于清单5中的XML。 但是,它们并不相同:

清单4.帐户XML代码段
<account>
 <id>3A-00</id>
 <name>acme</name>
</account>

清单5中的XML代码片段与清单4中的逻辑文档相同。但是,XMLUnit认为它们不是相同的,因为nameid元素已交换。

清单5.一个类似的XML代码片段
<account>
 <name>acme</name>
 <id>3A-00</id>
</account>

因此,我可以编写一个测试用例来验证XMLUnit的行为,如清单6所示:

清单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所示:

清单7.验证没有属性值的XML结构
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那样)并提供自定义实现。

具有XPath的XMLUnit

为了完成XML测试帽技巧,XMLUnit有助于使用XPath验证XML文档的特定部分。

例如,使用清单1中相同的文档格式,我想验证我的应用程序生成的第一个Class元素的name属性值是com.acme.web.Widget 。 为此,我必须创建一个XPath表达式以导航到确切的位置。 此外,XMLUnit的XMLTestCase提供了一个方便的assertXpathExists()方法,这意味着我现在必须扩展XMLTestCase

清单8.使用XPath验证精确的XML值
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

 类似资料: