XMLUnit: 验证比较XML文件的工具

By | 2015年11月11日

前言

我在用String直接比较两个XML时,attribute的顺序不一致导致unit test fails in Jenkins,这个库完美的解决了该问题。Nexus上传的是V1.6,最新版是2.0,被完全重写,但是还没有release。 XMLUnit支持Java和.Net。

XMLUnit简介

XMLUnit使用Junit的assert风格来比较XML文档的内容和格式,它是一个开源项目。XMLUnit本身是服务一个测试系统,该系统生成并接收大量的XML文件,需要比较生成的XML和接收的XML内容是否一致。使用DTD或Schema只能验证一个XML是否正确,却不能比较两个XML的内容。

比较原理

XMLUnit比较两个XML的基本单元是difference,如果两个XML片段没有difference,则他们是identical;如果它们的difference是recoverable的,则两个XML是similiar;如果difference是unrecoverable的,则两个XML是不同的。

通常来说,两个XML文件元素或节点内容相同,顺序不同,是similiar;如果顺序也相同,则是identical。(详细内容请看:XMLUnit-Java.pdf, Chapter 3)

继承XMLTestCase比较XML

使用继承XMLTestCase方式中,assertXMLEqual(“Compare  message”, xml1, xml2)时,XMLTestCase实际上市创建了一个Diff对象,Diff对象保存了比较的结果,并且提供了similar()方法和identical()方法。

  • assertXMLEqual()实际上是调用Diff.similar()方法,比较相似
  • assertXMLIdentical()实际上是调用Diff.identical(),比较相等

D4C中通常比较的是两个XML文件是否相当,所以应当调用assertXMLIdentical();

不继承XMLTestCase,手动创建Diff比较XML

如果不想继承XMLTestCase,我们可以手动创建Diff来比较两个XML文件:

继承XMLTestCase
String expected = "<root></root>";
String target = "<people></people>";
//Diff myDiff = new Diff(expected, target);
Diff myDiff = XMLUnit.compareXML(expected, target);
assertTrue("XML similar " + myDiff.toString(), myDiff.similar());
assertTrue("XML identical " + myDiff.toString(), myDiff.identical());

myDiff.toString()中包含了两个XML文件的详细不匹配信息(XPath格式),帮助我们修复test。例如: junit.framework.AssertionFailedError: XML similar org.custommonkey.xmlunit.Diff [different] Expected element tag name ‘root’ but was ‘people’ – comparing <root…> at /root[1] to <people…> at /people[1]

获得比较的所有Difference

构建Diff对象的时候,只要碰到Difference,就会停下。要想得到两个XML文件的所有Difference,需要DetailedDiff,构建DetailedDiff要先创建Diff。

Get all differences
DetailedDiff detailedDiff = new DetailedDiff(new Diff(expected, target));
List allDiffereneces = detailedDiff.getAllDifferences();
System.out.println(allDiffereneces.toString());

比较两个XML文件的格式

忽略节点的值和属性值:

Diff myDiff = XMLUnit.compareXML(expected, target);
myDiff.overrideDifferenceListener(new IgnoreTextAndAttributeValuesDifferenceListener());

这个类的名字好长,看来类的名字可以很长,如果这样能够让别人理解这个类的功能。我们也可以implement DifferenceListener来自定义,用以满足自己的比较需求。

修改XMLUnit的xml解析引擎

XMLUnit.setControlParser(“org.apache.xerces.jaxp.DocumentBuilderFactoryImpl”); XMLUnit.setTestParser(“org.apache.xerces.jaxp.DocumentBuilderFactoryImpl”); XMLUnit.setSAXParserFactory(“org.apache.xerces.jaxp.SAXParserFactoryImpl”); 可以使用任何的JAXP引擎,如: org.apache.xerces.jaxp.DocumentBuilderFactoryImpl org.apache.xerces.jaxp.SAXParserFactoryImpl org.apache.xalan.processor.TransformerFactoryImpl

Issues

如果test case继承了XMLTestCase,Junit中的@Before, @BeforeClass, @After, @AfterClass将不起作用,但是@Test还会起作用。

忽略Element content的whitespace

XMLUnit.setIgnoreWhitespace(true); 去掉element content的头尾空白,相当于是trim。 下面两个XML片段,assert结果是identical:

XMLUnit.setIgnoreWhitespace(true);
String xml1 = "<root>BOOK</root>";
String xml2 = "<root> BOOK </root>";

忽略注释

XMLUnit.setIgnoreComments(true); 下面两个XML片段,assert结果是identical:

XMLUnit.setIgnoreComments(true);
String xml1 = "<root>BOOK</root>";
String xml2 = "<root>BOOK<!--This is comment--></root><!--Comment-->";

忽略CDATA

下面两个XML片段,是identical

XMLUnit.setIgnoreDiffBetweenTextAndCDATA(true);
String xml1 = "<root></root>";
String xml2 = "<root><![CDATA[<bar>]]></root>";

忽略节点属性的顺序

XMLUnit.setIgnoreAttributeOrder(true);

我将setIgnoreAttributeOrder设置成false,下面的XML片段依然是identical,也许是XMLUnit将String解析成XML时,重新生成attribute的顺序了。

XMLUnit.setIgnoreAttributeOrder(false);
String xml1 = "<model name='tachyon-model' id='001'/>";
String xml2 = "<model id='001' name='tachyon-model'/>";

使用XPath局部验证XML

前提,需要继承XMLTestCase。适用于XML很大,确定其它地方没有改动,只有个别节点被改动了,为了快速,只需要验证被改动的节点。关于XPath的使用还有很多,下面是最基本的使用。

String xmlFile = "<root><aberration-file id='009'>aberration.xml</aberration-file></root>";
// Whether exist an 'aberration-file' element whose content is 'aberration.xl';
assertXpathExists("//aberration-file['aberration.xml']", xmlFile);
// Whether exist an 'aberration-file' element whose id is '009'
assertXpathExists("//aberration-file[@id='009']", xmlFile);

asdf

参考文档:

  1. XMLUnit1.6官方手册 (XMLUnit-Java.pdf)
  2. http://www.ibm.com/developerworks/cn/java/j-cq121906.html