XMLUnit 2.xの紹介

1概要

XMLUnit 2.x は、XMLコンテンツのテストと検証に役立つ強力なライブラリであり、XMLに何が含まれるべきかを正確に知っているときに特に役立ちます。

したがって、主に単体テスト の中でXMLUnitを使用して、有効なXML であることを確認し、特定の情報が含まれているか、特定のスタイル文書に準拠していることを確認します。

さらに、XMLUnitを使用すると、どのような違いが重要か、またスタイル参照のどの部分を比較XMLのどの部分と比較するかを制御できます。

XMLUnit 1.xではなくXMLUnit 2.xに焦点を合わせているので、XMLUnitという単語を使用するときはいつでも、2.xを厳密に参照しています。

最後に、私たちはアサーションにもHamcrestのマッチャーを使用しますので、リンクをブラッシュアップすることをお勧めします。

2 XMLUnit Mavenのセットアップ

私たちのMavenプロジェクトでこのライブラリを使用するには、 pom.xml に次の依存関係が必要です。

<dependency>
    <groupId>org.xmlunit</groupId>
    <artifactId>xmlunit-core</artifactId>
    <version>2.2.1</version>
</dependency>

xmlunit-core の最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22xmlunit-core%22[このリンク]をたどることで見つけることができます。そして:

<dependency>
    <groupId>org.xmlunit</groupId>
    <artifactId>xmlunit-matchers</artifactId>
    <version>2.2.1</version>
</dependency>

xmlunit-matchers の最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22xmlunit- matchers%22[このリンク]で入手できます。

3 XMLの比較

3.1. 単純な違いの例

XMLが2つあるとしましょう。ドキュメント内のノードの内容と順序がまったく同じであれば、それらは同一であると見なされるため、次のテストに合格します。

@Test
public void given2XMLS__whenIdentical__thenCorrect() {
    String controlXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    String testXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    assertThat(testXml, CompareMatcher.isIdenticalTo(controlXml));
}

この次のテストは、2つのXMLが似ていますが、ノードが異なる順序で出現しているため同一ではないため、失敗します。

@Test
public void given2XMLSWithSimilarNodesButDifferentSequence__whenNotIdentical__thenCorrect() {
    String controlXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    String testXml = "<struct><boolean>false</boolean><int>3</int></struct>";
    assertThat(testXml, assertThat(testXml, not(isIdenticalTo(controlXml)));
}

3.2. 詳細な違いの例

上記の2つのXML文書の違いは Difference Engine によって検出されます。

デフォルトでは効率のために、最初の違いが見つかるとすぐに比較プロセスを停止します。

2つのXMLの間のすべての違いを得るために、 Diff クラスのインスタンスを使います。

@Test
public void given2XMLS__whenGeneratesDifferences__thenCorrect(){
    String controlXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    String testXml = "<struct><boolean>false</boolean><int>3</int></struct>";
    Diff myDiff = DiffBuilder.compare(controlXml).withTest(testXml).build();

    Iterator<Difference> iter = myDiff.getDifferences().iterator();
    int size = 0;
    while (iter.hasNext()) {
        iter.next().toString();
        size++;
    }
    assertThat(size, greaterThan(1));
}

while ループで返された値を出力すると、結果は以下のようになります。

Expected element tag name 'int' but was 'boolean' -
  comparing <int...> at/struct[1]/int[1]to <boolean...>
    at/struct[1]/boolean[1](DIFFERENT)
Expected text value '3' but was 'false' -
  comparing <int ...>3</int> at/struct[1]/int[1]/text()[1]to
    <boolean ...>false</boolean> at/struct[1]/boolean[1]/text()[1](DIFFERENT)
Expected element tag name 'boolean' but was 'int' -
  comparing <boolean...> at/struct[1]/boolean[1]
    to <int...> at/struct[1]/int[1](DIFFERENT)
Expected text value 'false' but was '3' -
  comparing <boolean ...>false</boolean> at/struct[1]/boolean[1]/text()[1]
    to <int ...>3</int> at/struct[1]/int[1]/text()[1](DIFFERENT)

各インスタンスは、コントロールノードとテストノードの間に見られる違いのタイプとそれらのノードの詳細(各ノードのXPathロケーションを含む)の両方を記述します。

最初の違いが見つかった後でDifference Engineを** 停止させ、それ以上の違いを列挙しない場合 - ComparisonController を指定する必要があります。

@Test
public void given2XMLS__whenGeneratesOneDifference__thenCorrect(){
    String myControlXML = "<struct><int>3</int><boolean>false</boolean></struct>";
    String myTestXML = "<struct><boolean>false</boolean><int>3</int></struct>";

    Diff myDiff = DiffBuilder
      .compare(myControlXML)
      .withTest(myTestXML)
      .withComparisonController(ComparisonControllers.StopWhenDifferent)
       .build();

    Iterator<Difference> iter = myDiff.getDifferences().iterator();
    int size = 0;
    while (iter.hasNext()) {
        iter.next().toString();
        size++;
    }
    assertThat(size, equalTo(1));
}
  • 違いのメッセージはより単純です:**

Expected element tag name 'int' but was 'boolean' -
  comparing <int...> at/struct[1]/int[1]
    to <boolean...> at/struct[1]/boolean[1](DIFFERENT)

** 4入力ソース

XMLUnitを使用すると、アプリケーションのニーズに合わせてさまざまなソースからXMLデータを選択できます。この場合、 Input クラスとその静的メソッドの配列を使用します。

プロジェクトルートにあるXMLファイルから入力を選択するには、次のようにします。

@Test
public void givenFileSource__whenAbleToInput__thenCorrect() {
    ClassLoader classLoader = getClass().getClassLoader();
    String testPath = classLoader.getResource("test.xml").getPath();
    String controlPath = classLoader.getResource("control.xml").getPath();

    assertThat(
      Input.fromFile(testPath), isSimilarTo(Input.fromFile(controlPath)));
}

XML文字列から入力ソースを選ぶには、次のようにします。

@Test
public void givenStringSource__whenAbleToInput__thenCorrect() {
    String controlXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    String testXml = "<struct><int>3</int><boolean>false</boolean></struct>";

    assertThat(
      Input.fromString(testXml),isSimilarTo(Input.fromString(controlXml)));
}

入力としてストリームを使用しましょう。

@Test
public void givenStreamAsSource__whenAbleToInput__thenCorrect() {
    assertThat(Input.fromStream(XMLUnitTests.class
      .getResourceAsStream("/test.xml")),
        isSimilarTo(
          Input.fromStream(XMLUnitTests.class
            .getResourceAsStream("/control.xml"))));
}

XMLUnitによって解決される有効なソースを渡す場合は、 Input.from(Object) を使用することもできます。

たとえば、ファイルを次の場所に渡すことができます。

@Test
public void givenFileSourceAsObject__whenAbleToInput__thenCorrect() {
    ClassLoader classLoader = getClass().getClassLoader();

    assertThat(
      Input.from(new File(classLoader.getResource("test.xml").getFile())),
      isSimilarTo(Input.from(new File(classLoader.getResource("control.xml").getFile()))));
}

または String:

@Test
public void givenStringSourceAsObject__whenAbleToInput__thenCorrect() {
    assertThat(
      Input.from("<struct><int>3</int><boolean>false</boolean></struct>"),
      isSimilarTo(Input.from("<struct><int>3</int><boolean>false</boolean></struct>")));
}

または Stream:

@Test
public void givenStreamAsObject__whenAbleToInput__thenCorrect() {
    assertThat(
      Input.from(XMLUnitTest.class.getResourceAsStream("/test.xml")),
      isSimilarTo(Input.from(XMLUnitTest.class.getResourceAsStream("/control.xml"))));
}

そしてそれらはすべて解決されます。

5特定のノードを比較する

上記のセクション2では、 xmlunit-core libraryの機能を使用して、類似のXMLには少しカスタマイズが必要なため、同一のXMLのみを調べました。

@Test
public void given2XMLS__whenSimilar__thenCorrect() {
    String controlXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    String testXml = "<struct><boolean>false</boolean><int>3</int></struct>";

    assertThat(testXml, isSimilarTo(controlXml));
}

XMLには似たようなノードがあるので上記のテストは成功するはずですが、失敗します。これは、 XMLUnitが、ルートノード に対して同じ深さでコントロールノードとテストノードを比較するためです。

そのため、 isSimilarTo 条件は、 isIdenticalTo 条件よりもテストが少し興味深いものです。 controlXml のノード <int> 3 </int> は、 testXml <boolean> false </boolean> と比較され、自動的に失敗メッセージが表示されます。

java.lang.AssertionError:
Expected: Expected element tag name 'int' but was 'boolean' -
  comparing <int...> at/struct[1]/int[1]to <boolean...> at/struct[1]/boolean[1]:
<int>3</int>
   but: result was:
<boolean>false</boolean>

XMLUnitの DefaultNodeMatcher クラスと ElementSelector クラスが役に立つのはここです

DefaultNodeMatcher クラスは、 controlXml、 のノードをループするときに比較段階でXMLUnitに問い合わせられ、 testXml のどのXMLノードが controlXml で検出された現在のXMLノードと比較されるかを決定します。

その前に、 DefaultNodeMatcher は既に ElementSelector を調べてノードのマッチング方法を決定しています。

我々のテストは失敗した。デフォルトの状態では、XMLUnitはXMLをトラバースするためにそしてドキュメントの順序に基づいてノードを一致させるために深さ優先のアプローチを使用するので、 <int> <boolean> と一致する。

合格するようにテストを調整しましょう。

@Test
public void given2XMLS__whenSimilar__thenCorrect() {
    String controlXml = "<struct><int>3</int><boolean>false</boolean></struct>";
    String testXml = "<struct><boolean>false</boolean><int>3</int></struct>";

    assertThat(testXml,
      isSimilarTo(controlXml).withNodeMatcher(
      new DefaultNodeMatcher(ElementSelectors.byName)));
}

この場合、XMLUnitが比較するノードを要求したときには、既にそれらの要素名でノードをソートして一致させる必要があることを DefaultNodeMatcher に伝えています。

最初に失敗した例は、 ElementSelectors.Default DefaultNodeMatcher に渡すのと似ていました。

代わりに、 xmlunit-matchers を使用するのではなく、 xmlunit-core Diff を使用することもできます。

@Test
public void given2XMLs__whenSimilarWithDiff__thenCorrect() throws Exception {
    String myControlXML = "<struct><int>3</int><boolean>false</boolean></struct>";
    String myTestXML = "<struct><boolean>false</boolean><int>3</int></struct>";
    Diff myDiffSimilar = DiffBuilder.compare(myControlXML).withTest(myTestXML)
      .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName))
      .checkForSimilar().build();

    assertFalse("XML similar " + myDiffSimilar.toString(),
      myDiffSimilar.hasDifferences());
}

6. カスタム DifferenceEvaluator

DifferenceEvaluator は、比較の結果を決定します。その役割は、比較結果の重大度を決定することに限定されています。

これは、2つのXMLが 同一 類似 、または 差異 のどれであるかを決定するクラスです。

次のXML部分を検討してください。

<a>
    <b attr="abc">
    </b>
</a>

そして:

<a>
    <b attr="xyz">
    </b>
</a>

デフォルトの状態では、 attr 属性の値が異なるため、技術的に異なると評価されます。テストを見てみましょう。

@Test
public void given2XMLsWithDifferences__whenTestsDifferentWithoutDifferenceEvaluator__thenCorrect(){
    final String control = "<a><b attr=\"abc\"></b></a>";
    final String test = "<a><b attr=\"xyz\"></b></a>";
    Diff myDiff = DiffBuilder.compare(control).withTest(test)
      .checkForSimilar().build();
    assertFalse(myDiff.toString(), myDiff.hasDifferences());
}

失敗メッセージ

java.lang.AssertionError: Expected attribute value 'abc' but was 'xyz' -
  comparing <b attr="abc"...> at/a[1]/b[1]/@attr
  to <b attr="xyz"...> at/a[1]/b[1]/@attr

属性をあまり気にしないのであれば、 DifferenceEvaluator の動作を変更して無視することができます。私達は私達の自身を作成することによってこれを行います。

public class IgnoreAttributeDifferenceEvaluator implements DifferenceEvaluator {
    private String attributeName;
    public IgnoreAttributeDifferenceEvaluator(String attributeName) {
        this.attributeName = attributeName;
    }

    @Override
    public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
        if (outcome == ComparisonResult.EQUAL)
            return outcome;
        final Node controlNode = comparison.getControlDetails().getTarget();
        if (controlNode instanceof Attr) {
            Attr attr = (Attr) controlNode;
            if (attr.getName().equals(attributeName)) {
                return ComparisonResult.SIMILAR;
            }
        }
        return outcome;
    }
}

次に、最初の失敗したテストを書き換えて、次のように独自の DifferenceEvaluator インスタンスを指定します。

@Test
public void given2XMLsWithDifferences__whenTestsSimilarWithDifferenceEvaluator__thenCorrect() {
    final String control = "<a><b attr=\"abc\"></b></a>";
    final String test = "<a><b attr=\"xyz\"></b></a>";
    Diff myDiff = DiffBuilder.compare(control).withTest(test)
      .withDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr"))
      .checkForSimilar().build();

    assertFalse(myDiff.toString(), myDiff.hasDifferences());
}

今回は合格です。

7. 検証

XMLUnitは、 Validator クラスを使用してXML検証を実行します。検証に使用するスキーマを渡しながら、 forLanguage factoryメソッドを使用してそのインスタンスを作成します。

スキーマはその場所へのURIとして渡され、XMLUnitは Languages クラスでサポートするスキーマの場所を定数として抽象化します。

通常、 Validator クラスのインスタンスを次のように作成します。

Validator v = Validator.forLanguage(Languages.W3C__XML__SCHEMA__NS__URI);

この手順の後、XMLに対して検証するための独自のXSDファイルがある場合は、単にそのソースを指定してから、XMLファイルのソースを使用して Validator 's svalidateInstanceメソッドを呼び出します。

たとえば students.xsd を例に挙げます。

<?xml version = "1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name='class'>
        <xs:complexType>
            <xs:sequence>
                <xs:element name='student' type='StudentObject'
                   minOccurs='0' maxOccurs='unbounded'/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="StudentObject">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="age" type="xs:positiveInteger"/>
        </xs:sequence>
        <xs:attribute name='id' type='xs:positiveInteger'/>
    </xs:complexType>
</xs:schema>

そして students.xml :

<?xml version = "1.0"?>
<class>
    <student id="393">
        <name>Rajiv</name>
        <age>18</age>
    </student>
    <student id="493">
        <name>Candie</name>
        <age>19</age>
    </student>
</class>

それではテストを実行しましょう。

@Test
public void givenXml__whenValidatesAgainstXsd__thenCorrect() {
    Validator v = Validator.forLanguage(Languages.W3C__XML__SCHEMA__NS__URI);
    v.setSchemaSource(Input.fromStream(
      XMLUnitTests.class.getResourceAsStream("/students.xsd")).build());
    ValidationResult r = v.validateInstance(Input.fromStream(
      XMLUnitTests.class.getResourceAsStream("/students.xml")).build());
    Iterator<ValidationProblem> probs = r.getProblems().iterator();
    while (probs.hasNext()) {
        probs.next().toString();
    }
    assertTrue(r.isValid());
}

検証の結果は、文書が正常に検証されたかどうかを示すブールフラグを含む ValidationResult のインスタンスです。

失敗した場合に備えて、 ValidationResult には __ValidationProblem sを含む Iterable も含まれています。 students with error.xmlというエラーのある新しいXMLを作成しましょう。 <student> の代わりに、開始タグはすべて </studet> です。

<?xml version = "1.0"?>
<class>
    <studet id="393">
        <name>Rajiv</name>
        <age>18</age>
    </student>
    <studet id="493">
        <name>Candie</name>
        <age>19</age>
    </student>
</class>

それからそれに対してこのテストを実行します。

@Test
public void givenXmlWithErrors__whenReturnsValidationProblems__thenCorrect() {
    Validator v = Validator.forLanguage(Languages.W3C__XML__SCHEMA__NS__URI);
    v.setSchemaSource(Input.fromStream(
       XMLUnitTests.class.getResourceAsStream("/students.xsd")).build());
    ValidationResult r = v.validateInstance(Input.fromStream(
      XMLUnitTests.class.getResourceAsStream("/students__with__error.xml")).build());
    Iterator<ValidationProblem> probs = r.getProblems().iterator();
    int count = 0;
    while (probs.hasNext()) {
        count++;
        probs.next().toString();
    }
    assertTrue(count > 0);
}

while ループのエラーを表示すると、エラーは次のようになります。

ValidationProblem { line=3, column=19, type=ERROR,message='cvc-complex-type.2.4.a:
  Invalid content was found starting with element 'studet'.
    One of '{student}' is expected.' }
ValidationProblem { line=6, column=4, type=ERROR, message='The element type "studet"
  must be terminated by the matching end-tag "</studet>".' }
ValidationProblem { line=6, column=4, type=ERROR, message='The element type "studet"
  must be terminated by the matching end-tag "</studet>".' }

8 XPath

XPath式がXMLに対して評価されると、一致する Nodes. を含む NodeList が作成されます。

このXMLの一部を teachers.xml というファイルに保存したとします。

<teachers>
    <teacher department="science" id='309'>
        <subject>math</subject>
        <subject>physics</subject>
    </teacher>
    <teacher department="arts" id='310'>
        <subject>political education</subject>
        <subject>english</subject>
    </teacher>
</teachers>

XMLUnitは、次に示すように、XPath関連のアサーションメソッドを多数提供しています。

teacher という名前のすべてのノードを取得し、それらに対して個別にアサーションを実行できます。

@Test
public void givenXPath__whenAbleToRetrieveNodes__thenCorrect() {
    Iterable<Node> i = new JAXPXPathEngine()
      .selectNodes("//teacher", Input.fromFile(new File("teachers.xml")).build());
    assertNotNull(i);
    int count = 0;
    for (Iterator<Node> it = i.iterator(); it.hasNext();) {
        count++;
        Node node = it.next();
        assertEquals("teacher", node.getNodeName());

        NamedNodeMap map = node.getAttributes();
        assertEquals("department", map.item(0).getNodeName());
        assertEquals("id", map.item(1).getNodeName());
        assertEquals("teacher", node.getNodeName());
    }
    assertEquals(2, count);
}

子ノードの数、各ノードの名前、および各ノードの属性を検証する方法に注目してください。 Node を取得した後、さらに多くのオプションが利用可能になります。

パスが存在することを確認するために、以下を実行できます。

@Test
public void givenXmlSource__whenAbleToValidateExistingXPath__thenCorrect() {
    assertThat(Input.fromFile(new File("teachers.xml")), hasXPath("//teachers"));
    assertThat(Input.fromFile(new File("teachers.xml")), hasXPath("//teacher"));
    assertThat(Input.fromFile(new File("teachers.xml")), hasXPath("//subject"));
    assertThat(Input.fromFile(new File("teachers.xml")), hasXPath("//@department"));
}

パスが存在しないことを確認するために、これが可能です。

@Test
public void givenXmlSource__whenFailsToValidateInExistentXPath__thenCorrect() {
    assertThat(Input.fromFile(new File("teachers.xml")), not(hasXPath("//sujet")));
}

XPathは、システムによって作成されたわずかな量の変化するコンテンツのみを含む、ドキュメントの大部分が既知の不変のコンテンツで構成される場合に特に便利です。

9結論

このチュートリアルでは、 XMLUnit 2.x の基本機能の大部分と、それらを使用してアプリケーションでXML文書を検証する方法を紹介しました。

これらすべての例とコードスニペットの完全な実装は、 XMLUnit GitHubプロジェクト にあります。