前言
我在用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文件:
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。
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><bar></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); |
参考文档:
- XMLUnit1.6官方手册 (XMLUnit-Java.pdf)
- http://www.ibm.com/developerworks/cn/java/j-cq121906.html