ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 494

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

XMLUnit Java User’s Guide

17 / 35

*Indicates that the nodes identified as being different should be

*interpreted as being similar.

*/

int RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR; /**

*Override return value for the <code>differenceFound</code> method.

*Indicates that the nodes identified as being similar should be

*interpreted as being different.

*/

int RETURN_UPGRADE_DIFFERENCE_NODES_DIFFERENT = 3;

The skippedComparison method is invoked if the DifferenceEngine encounters two Nodes it cannot compare. Before invoking skippedComparison DifferenceEngine will have invoked differenceFound with a Difference of type NODE_TYPE.

A custom DifferenceListener that ignored any DOCTYPE related differences could be written as:

Example 3.3 A DifferenceListener that Ignores DOCTYPE Differences

public class IgnoreDoctype implements DifferenceListener { private static final int[] IGNORE = new int[] {

DifferenceConstants.HAS_DOCTYPE_DECLARATION_ID, DifferenceConstants.DOCTYPE_NAME_ID, DifferenceConstants.DOCTYPE_PUBLIC_ID_ID, DifferenceConstants.DOCTYPE_SYSTEM_ID_ID

};

static { Arrays.sort(IGNORE);

}

public int differenceFound(Difference difference) {

return Arrays.binarySearch(IGNORE, difference.getId()) >= 0 ? RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL

: RETURN_ACCEPT_DIFFERENCE;

}

public void skippedComparison(Node control, Node test) {

}

}

Apart from Diff and DetailedDiff XMLUnit ships with an additional implementation of DifferenceListener.

3.3.1 IgnoreTextAndAttributeValuesDifferenceListener

IgnoreTextAndAttributeValuesDifferenceListener doesn’t do anything in skippedComparison. It "downgrades" Differences of type ATTR_VALUE, ATTR_VALUE_EXPLICITLY_SPECIFIED and TEXT_VALUE to recoverable differences.

This means if instances of IgnoreTextAndAttributeValuesDifferenceListener are used together with Diff then two pieces of XML will be considered similar if they have the same basic structure. They are not considered identical, though.

Note that the list of ignored differences doesn’t cover all textual differences. You should configure XMLUnit to ignore comments and whitespace and to consider CDATA sections and text nodes to be the same (see Section 3.8) in order to cover COMMENT_V ALUE and CDATA_VALUE as well.

3.4 ElementQualifier

When DifferenceEngine encounters a list of DOM Elements as children of another Element it will ask the configured ElementQualifier which Element of the control piece of XML should be compared to which of the test piece. Its contract

XMLUnit Java User’s Guide

18 / 35

is:

/**

*Determine whether two elements are comparable

*@param control an Element from the control XML NodeList

*@param test an Element from the test XML NodeList

*@return true if the elements are comparable, false otherwise

*/

boolean qualifyForComparison(Element control, Element test);

For any given Element in the control piece of XML DifferenceEngine will cycle through the corresponding list of Elements in the test piece of XML until qualifyForComparison has returned true or the test document is exhausted.

When using DifferenceEngine or Diff it is completely legal to set the ElementQualifier to null. In this case any kind of Node is compared to the test Node that appears at the same position in the sequence.

Example 3.4 Example Nodes for ElementQualifier (the comments are not part of the example)

 

<!-- control piece of XML -->

 

 

 

 

 

<parent>

 

 

 

 

 

<child1/>

<!-- control node 1

-->

 

 

<child2/>

<!-- control node 2

-->

 

 

<child2 foo="bar">xyzzy</child2>

<!-- control node 3

-->

 

 

<child2 foo="baz"/>

<!-- control node 4

-->

 

 

</parent>

 

 

 

 

 

<!-- test piece of XML -->

 

 

 

 

 

<parent>

 

 

 

 

 

<child2 foo="baz"/>

<!-- test node 1

-->

 

 

<child1/>

<!-- test node 2

-->

 

 

<child2>xyzzy</child2>

<!-- test node 3

-->

 

 

<child2 foo="bar"/>

<!-- test node 4

-->

 

 

</parent>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Taking Example 3.4 without any ElementQualifier DifferenceEngine will compare control node n to test node n for n between 1 and 4. In many cases this is exactly what is desired, but sometimes <a><b/><c/></a> should be similar to <a><c/><b/></a> because the order of elements doesn’t matter - this is when you’d use a different ElementQualifier. XMLUnit ships with several implementations.

3.4.1 ElementNameQualifier

Only Elements with the same name - and Namespace URI if present - qualify.

In Example 3.4 this means control node 1 will be compared to test node 2. Then control node 2 will be compared to test node 3 because DifferenceEngine will start to search for the matching test Element at the second test node, the same sequence number the control node is at. Control node 3 is compared to test node 3 as well and control node 4 to test node 4.

3.4.2 ElementNameAndAttributeQualifier

Only Elements with the same name - and Namespace URI if present - as well as the same values for all attributes given in

ElementNameAndAttributeQualifier’s constructor qualify.

Let’s say "foo" has been passed to ElementNameAndAttributeQualifier’s constructor when looking at Example 3.4. This again means control node 1 will be compared to test node 2 since they do have the same name and no value at all for attribute "foo". Then control node 2 will be compared to test node 3 - again, no value for "foo". Control node 3 is compared to test node 4 as they have the same value "bar". Finally control node 4 is compared to test node 1; here DifferenceEngine searches from the beginning of the test node list after test node 4 didn’t match.

There are three constructors in ElementNameAndAttributeQualifier. The no-arg constructor creates an instance that compares all attributes while the others will compare a single attribute or a given subset of all attributes.


XMLUnit Java User’s Guide

19 / 35

3.4.3 ElementNameAndTextQualifier

Only Elements with the same name - and Namespace URI if present - as well as the same text content nested into them qualify.

In Example 3.4 this means control node 1 will be compared to test node 2 since they both don’t have any nested text at all. Then control node 2 will be compared to test node 4. Control node 3 is compared to test node 3 since they have the same nested text and control node 4 to test node 4.

3.4.4 org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier

All ElementQualifiers seen so far only looked at the Elements themselves and not at the structure nested into them at a deeper level. A frequent user question has been which ElementQualifier should be used if the pieces of XML in Example 3.5 should be considered similar.

Example 3.5 Example for RecursiveElementNameAndTextQualifier (the comments are not part of the example)

 

<!-- control -->

 

 

 

<table>

 

 

 

<tr>

<!-- control row 1 -->

 

 

<td>foo</td>

 

 

 

</tr>

 

 

 

<tr>

<!-- control row 2 -->

 

 

<td>bar</td>

 

 

 

</tr>

 

 

 

</table>

 

 

 

<!-- test -->

 

 

 

<table>

 

 

 

<tr>

<!-- test row 1 -->

 

 

<td>bar</td>

 

 

 

</tr>

 

 

 

<tr>

<!-- test row 2 -->

 

 

<td>foo</td>

 

 

 

</tr>

 

 

 

</table>

 

 

 

 

 

 

 

 

 

 

At first glance ElementNameAndTextQualifier should work but it doesn’t. When DifferenceEngine processed the children of table it would compare control row 1 to test row 1 since both tr elements have the same name and both have no textual content at all.

What is needed in this case is an ElementQualifier that looks at the element’s name, as well as the name of the first child element and the text nested into that first child element. This is what RecursiveElementNameAndTextQualifier does.

RecursiveElementNameAndTextQualifier ignores whitespace between the elements leading up to the nested text.

3.4.5 org.custommonkey.xmlunit.examples.MultiLevelElementNameAndTextQualifier

MultiLevelElementNameAndTextQualifier has in a way been the predecessor of Section 3.4.4. It also matches element names and those of nested child elements until it finds matches, but unlike RecursiveElementNameAndTextQua lifier, you must tell MultiLevelElementNameAndTextQualifier at which nesting level it should expect the nested text.

MultiLevelElementNameAndTextQualifier’s constructor expects a single argument which is the nesting level of the expected text. If you use an argument of 1, MultiLevelElementNameAndTextQualifier is identical to ElementNa meAndTextQualifier. In Example 3.5 a value of 2 would be needed.

By default MultiLevelElementNameAndTextQualifier will not ignore whitespace between the elements leading up to the nested text. If your piece of XML contains this sort of whitespace (like Example 3.5 which contains a newline and several space characters between <tr> and <td>) you can either instruct XMLUnit to ignore whitespace completely (see Section 3.8.1)



XMLUnit Java User’s Guide

20 / 35

or use the two-arg constructor of MultiLevelElementNameAndTextQualifier introduced with XMLUnit 1.2 and set the ignoreEmptyTexts argument to true.

In general RecursiveElementNameAndTextQualifier requires less knowledge upfront and its whitespace-handling is more intuitive.

3.5 Diff and DetailedDiff

Diff and DetailedDiff provide simplified access to DifferenceEngine by implementing the ComparisonContr oller and DifferenceListener interfaces themselves. They cover the two most common use cases for comparing two pieces of XML: checking whether the pieces are different (this is what Diff does) and finding all differences between them (this is what DetailedDiff does).

DetailedDiff is a subclass of Diff and can only be constructed by creating a Diff instance first.

The major difference between them is their implementation of the ComparisonController interface: DetailedDiff will never stop the comparison since it wants to collect all differences. Diff in turn will halt the comparison as soon as the first Difference is found that is not recoverable. In addition DetailedDiff collects all Differences in a list and provides access to it.

By default Diff will consider two pieces of XML as identical if no differences have been found at all, similar if all differences that have been found have been recoverable (see Table 1 to Table 4) and different as soon as any non-recoverable difference has been found.

It is possible to specify a DifferenceListener to Diff using the overrideDifferenceListener method. In this case each Difference will be evaluated by the passed in DifferenceListener. By returning RETURN_IGNOR E_DIFFERENCE_NODES_IDENTICAL the custom listener can make Diff ignore the difference completely. Likewise any

Difference for which the custom listener returns RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR will be treated as if the Difference was recoverable.

There are several overloads of the Diff constructor that allow you to specify your piece of XML in many ways. There are overloads that accept additional DifferenceEngine and ElementQualifier arguments. Passing in a DifferenceEn gine of your own is the only way to use a ComparisonController other than Diff.

Note that Diff and DetailedDiff use ElementNameQualifier as their default ElementQualifier. This is different from DifferenceEngine which defaults to no ElementQualifier at all.

To use a custom ElementQualifier you can also use the overrideElementQualifier method. Use this with an argument of null to unset the default ElementQualifier as well.

To compare two pieces of XML you’d create a Diff instance from those two pieces and invoke identical to check that there have been no differences at all and similar to check that any difference, if any, has been recoverable. If the pieces are identical they are also similar. Likewise if they are not similar they can’t be identical either.

Example 3.6 Comparing Two Pieces of XML Using Diff

Diff d = new Diff("<a><b/><c/></a>", "<a><c/><b/></a>");

assertFalse(d.identical()); // CHILD_NODELIST_SEQUENCE Difference

assertTrue(d.similar());

The result of the comparison is cached in Diff, repeated invocations of identical or similar will not reevaluate the pieces of XML.

DetailedDiff provides only a single constructor that expects a Diff as argument. Don’t use DetailedDiff if all you need to know is whether two pieces of XML are identical/similar - use Diff directly since its short-cut ComparisonContro ller implementation will save time in this case.


XMLUnit Java User’s Guide

21 / 35

Example 3.7 Finding All Differences Using DetailedDiff

Diff d = new Diff("<a><b/><c/></a>", "<a><c/><b/></a>"); DetailedDiff dd = new DetailedDiff(d); dd.overrideElementQualifier(null); assertFalse(dd.similar());

List l = dd.getAllDifferences();

assertEquals(2, l.size()); // expected <b/> but was <c/> and vice versa

3.6 MatchTracker

Sometimes you might be interested in any sort of comparison result and want to get notified of successful matches as well. Maybe you want to provide feedback on the amount of differences and similarities between two documents, for example.

The interface MatchTracker can be implemented to get notified on each and every successful match, note that there may be a lot more comparisons going on than you might expect and that your callback gets notified a lot.

Example 3.8 The MatchTracker interface

package org.custommonkey.xmlunit;

/**

*Listener for callbacks from a {@link DifferenceEngine#compare

*DifferenceEngine comparison} that is notified on each and every

*comparision that resulted in a match.

*/

public interface MatchTracker { /**

*Receive notification that 2 match.

*@param match a Difference instance as defined in {@link

*DifferenceConstants DifferenceConstants} describing the test

*that matched and containing the detail of the nodes that have

*been compared

*/

void matchFound(Difference difference);

}

Despite its name the Difference instance passed into the matchFound method really describes a match and not a difference. You can expect that the getValue method on both the control and the test NodeDetail will be equal.

DifferenceEngine provides a constructor overload that allows you to pass in a MatchTracker instance and also provides a setMatchTracker method. Diff and DetailedDiff provide overrideMatchTracker methods that fill the same purpose.

Note that your MatchTracker won’t receive any callbacks once the configured ComparisonController has decided that DifferenceEngine should halt the comparison.

3.7JUnit 3.x Convenience Methods

XMLAssert and XMLTestCase contain quite a few overloads of methods for comparing two pieces of XML.

The method’s names use the word Equal to mean the same as similar in the Diff class (or throughout this guide). So assertXMLEqual will assert that only recoverable differences have been encountered where assertXMLNotEqual asserts that some differences have been non-recoverable. assertXMLIdentical asserts that there haven’t been any differences at all while assertXMLNotIdentical asserts that there have been differences (recoverable or not).

XMLUnit Java User’s Guide

22 / 35

Most of the overloads of assertXMLEqual just provide different means to specify the pieces of XML as Strings, InputS ources, Readers9 or Documents. For each method there is a version that takes an additional err argument which is used to create the message if the assertion fails.

If you don’t need any control over the ElementQualifier or DifferenceListener used by Diff these methods will save some boilerplate code. If CONTROL and TEST are pieces of XML represented as one of the supported inputs then

Diff d = new Diff(CONTROL, TEST);

assertTrue("expected pieces to be similar, " + d.toString(), d.similar());

and

assertXMLEqual("expected pieces to be similar", CONTROL, TEST);

are equivalent.

If you need more control over the Diff instance there is a version of assertXMLEqual (and assertXMLIdentical) that accepts a Diff instance as its argument as well as a boolean indicating whether you expect the Diff to be similar

(identical) or not.

XMLTestCase contains a couple of compareXML methods that really are only shortcuts to Diff’s constructors.

There is no way to use DifferenceEngine or DetailedDiff directly via the convenience methods.

3.8Configuration Options

Unless you are using Document or DOMSource overrides when specifying your pieces of XML, XMLUnit will use the configured XML parsers (see Section 2.4.1) and EntityResolvers (see Section 2.4.2). There are configuration options to use different settings for the control and test pieces of XML.

In addition some of the other configuration settings may lead to XMLUnit using the configured XSLT transformer (see Section 2.4.1) under the covers.

3.8.1Whitespace Handling

Two different configuration options affect how XMLUnit treats whitespace in comparisons:

Element Content Whitespace (see Section 2.4.3)

If XMLUnit has been configured to ignore element content whitespace it will trim any text nodes found by the parser. This means that there won’t appear to be any textual content in element <foo> for the following example. If you don’t set XMLUnit.setIgnoreWhitespace there would be textual content consisting of a new line character.

<foo>

</foo>

At the same time the following two <foo> elements will be considered identical if the option has been enabled, though.

<foo>bar</foo> <foo> bar </foo>

When this option is set to true, Diff will use the XSLT transformer under the covers.

"Normalizing" Whitespace If you set XMLUnit.setNormalizeWhitespace to true then XMLUnit will replace any kind of whitespace found in character content with a SPACE character and collapse consecutive whitespace characters to a single SPACE. It will also trim the resulting character content on both ends.

The following two <foo> elements will be considered identical if the option has been set:

9 See Section 2.5 for some advice on choosing your input format.