xmlunit

    技术2024-01-23  107

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

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

    提高代码质量

    不要错过安德鲁(Andrew)随附的讨论论坛 ,以获取有关代码指标,测试框架以及编写注重质量的代码的帮助。

    旧的String比较好

    首先,让我们假设您已经构建了一个应用程序,该应用程序输出代表对象相关性报告的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显然是由应用程序生成的; 因此,第一级测试是验证应用程序实际上可以生成文档。 验证之后,您将至少要测试特定文档的三个方面:

    结构体 内容 具体内容

    您可以使用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与TestNG?

    XMLUnit是JUnit扩展,但这并不一定意味着您不能在TestNG中使用它。 您可以将几乎任何框架都集成到TestNG中,只要它具有支持委托且不基于装饰器的API。

    使用XMLUnit进行测试

    当您感觉到自己在努力工作时,通常可以认为其他人已经找到了解决问题的简便方法。 在以编程方式验证XML文档时,想到的解决方案是XMLUnit。

    XMLUnit是一个JUnit扩展框架,可帮助开发人员测试XML文档。 实际上,XMLUnit是一个名副其实的XML测试帽子戏法:您可以使用它来验证XML文档的结构,其内容,甚至文档的特定部分。

    最简单的操作是使用XMLUnit在逻辑上将运行时XML文档与预定义的有效控制文件进行比较。 本质上,这是一个差异测试 :给定您知道正确的XML文档,应用程序在运行时是否会生成相同的东西? 这是一个相对简单的测试,但是您可以使用它来验证XML文档的结构和内容。 您也可以在XPath的帮助下验证特定内容。

    委托,不要继承!

    根据经验,应尽可能避免继承测试用例。 许多JUnit扩展框架,包括XMLUnit,都提供了专门的测试用例,可以从中继承这些专用用例,以方便测试特定的体系结构。 但是,由于Java平台的单继承范式,从框架继承类的测试用例具有灵活性。 通常,这些相同的JUnit扩展框架提供了委托API,这使得可以轻松组合各种框架而无需采用严格的继承结构。

    验证内容

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

    与Diff的比较

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

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

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

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

    清单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()); }
    类似不一样!

    使用IgnoreTextAndAttributeValuesDifferenceListener类时,必须断言两个文档是similar 且不 identical 。 如果您错误地调用identical的属性值,则将对其进行处理。

    当然,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也有帮助!

    X什么?

    XPath或XML路径语言是一种表达语言,用于基于树表示寻址XML文档的各个部分。 XPath允许您浏览XML文档,并有助于选择文档值。

    为什么要努力工作?

    XMLUnit是一个基于Java的开源工具,与使用String比较可以做的任何事情相比,它使测试XML文档变得更加容易和灵活。 使用XMLUnit进行差异测试的唯一可能缺点是,测试将依赖文件系统来加载控制文档。 在编写测试时,请考虑这种增加的依赖性。

    尽管XMLUnit已有一段时间没有发布任何新更新,但其当前的功能集合足够强大,足以为测试带来巨大的成功-在这种情况下,它基本上是免费的!


    翻译自: https://www.ibm.com/developerworks/java/library/j-cq121906/index.html

    相关资源:xmlunit-1.5.jar
    Processed: 0.027, SQL: 9