Einführung in XMLUnit 2.x

Einführung in XMLUnit 2.x

1. Überblick

XMLUnit 2.x ist eine leistungsstarke Bibliothek, mit der wir XML-Inhalte testen und überprüfen können. Sie ist besonders nützlich, wenn wir genau wissen, was XML enthalten sollte.

Daher verwenden wir XMLUnit hauptsächlich in Unit-Teststo verify that what we have is valid XML, die bestimmte Informationen enthalten oder einem bestimmten Stildokument entsprechen.

Mit XMLUnit außerdemwe have control over what kind of difference is important to us und welcher Teil der Stilreferenz mit welchem ​​Teil Ihres Vergleichs-XML verglichen werden soll.

Da wir uns auf XMLUnit 2.x und nicht auf XMLUnit 1.x konzentrieren, beziehen wir uns immer auf 2.x, wenn wir das Wort XMLUnit verwenden.

Schließlich werden wir auch Hamcrest-Matcher für Behauptungen verwenden. Daher ist es eine gute Idee,Hamcrestaufzufrischen, falls Sie nicht damit vertraut sind.

2. XMLUnit Maven Setup

Um die Bibliothek in unseren Maven-Projekten verwenden zu können, müssen die folgenden Abhängigkeiten inpom.xml vorhanden sein:


    org.xmlunit
    xmlunit-core
    2.2.1

Die neueste Version vonxmlunit-core finden Sie unterthis link. And:


    org.xmlunit
    xmlunit-matchers
    2.2.1

Die neueste Version vonxmlunit-matchers ist unterthis link verfügbar.

3. XML vergleichen

3.1. Einfache Differenzbeispiele

Nehmen wir an, wir haben zwei XML-Teile. Sie gelten als identisch, wenn der Inhalt und die Reihenfolge der Knoten in den Dokumenten genau gleich sind, sodass der folgende Test bestanden wird:

@Test
public void given2XMLS_whenIdentical_thenCorrect() {
    String controlXml = "3false";
    String testXml = "3false";
    assertThat(testXml, CompareMatcher.isIdenticalTo(controlXml));
}

Dieser nächste Test schlägt fehl, da die beiden XML-Teile ähnlich, aber nicht identisch mit ihrennodes occur in a different sequence sind:

@Test
public void given2XMLSWithSimilarNodesButDifferentSequence_whenNotIdentical_thenCorrect() {
    String controlXml = "3false";
    String testXml = "false3";
    assertThat(testXml, assertThat(testXml, not(isIdenticalTo(controlXml)));
}

3.2. Detailliertes Differenzbeispiel

Unterschiede zwischen zwei oben genannten XML-Dokumenten werden durchDifference Engine erkannt.

Standardmäßig und aus Effizienzgründen wird der Vergleich abgebrochen, sobald der erste Unterschied festgestellt wird.

Um alle Unterschiede zwischen zwei XML-Teilen zu ermitteln, verwenden wir eine Instanz der KlasseDiffwie folgt:

@Test
public void given2XMLS_whenGeneratesDifferences_thenCorrect(){
    String controlXml = "3false";
    String testXml = "false3";
    Diff myDiff = DiffBuilder.compare(controlXml).withTest(testXml).build();

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

Wenn wir die in derwhile-Schleife zurückgegebenen Werte drucken, ist das Ergebnis wie folgt:

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

Jede Instanz beschreibt sowohl den Unterschiedstyp zwischen einem Steuerknoten und einem Testknoten als auch die Details dieser Knoten (einschließlich der XPath-Position jedes Knotens).

Wenn wir die Differenz-Engine aufstop after the first difference is found zwingen und keine weiteren Unterschiede aufzählen möchten, müssen wirComparisonController angeben:

@Test
public void given2XMLS_whenGeneratesOneDifference_thenCorrect(){
    String myControlXML = "3false";
    String myTestXML = "false3";

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

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

Die Differenzmeldung ist einfacher:

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

4. Eingabequellen

Mit XMLUnit, können wir XML-Daten aus einer Vielzahl von Quellen auswählen, die für die Anforderungen unserer Anwendung geeignet sind. In diesem Fall verwenden wir die KlasseInput mit ihrem Array statischer Methoden.

Um Eingaben aus einer XML-Datei im Projektstamm auszuwählen, gehen Sie wie folgt vor:

@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)));
}

So wählen Sie eine Eingabequelle aus einer XML-Zeichenfolge aus:

@Test
public void givenStringSource_whenAbleToInput_thenCorrect() {
    String controlXml = "3false";
    String testXml = "3false";

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

Verwenden wir jetzt einen Stream als Eingabe:

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

Wir könnten auchInput.from(Object) verwenden, bei denen wir eine gültige Quelle übergeben, die von XMLUnit aufgelöst werden soll.

Zum Beispiel können wir eine Datei übergeben in:

@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()))));
}

Oder aString:

@Test
public void givenStringSourceAsObject_whenAbleToInput_thenCorrect() {
    assertThat(
      Input.from("3false"),
      isSimilarTo(Input.from("3false")));
}

Oder aStream:

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

und sie werden alle gelöst.

5. Vergleichen bestimmter Knoten

In Abschnitt 2 oben haben wir uns nur mit identischem XML befasst, da für ähnliches XML einige Anpassungen mithilfe von Funktionen aus der Bibliothek vonxmlunit-coreerforderlich sind:

@Test
public void given2XMLS_whenSimilar_thenCorrect() {
    String controlXml = "3false";
    String testXml = "false3";

    assertThat(testXml, isSimilarTo(controlXml));
}

Der obige Test sollte bestanden werden, da die XMLs ähnliche Knoten haben. Er schlägt jedoch fehl. Dies liegt daran, dassXMLUnit compares control and test nodes at the same depth relative to the root node.

Daher ist der Zustand einesisSimilarToetwas interessanter zu testen als der Zustand einesisIdenticalTo. Der Knoten<int>3</int> incontrolXml wird mit<boolean>false</boolean> intestXml verglichen, wobei automatisch eine Fehlermeldung ausgegeben wird:

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

Hier bieten sich die KlassenDefaultNodeMatcher undElementSelectorvon XMLUnit an

Die KlasseDefaultNodeMatcherwird von XMLUnit in der Vergleichsphase konsultiert, während sie Knoten voncontrolXml, durchläuft, um zu bestimmen, welcher XML-Knoten vontestXml mit dem aktuellen XML-Knoten verglichen werden soll, auf den sie incontrolXmltrifft. s.

Zuvor hatDefaultNodeMatcher bereitsElementSelector konsultiert, um zu entscheiden, wie Knoten abgeglichen werden sollen.

Unser Test ist fehlgeschlagen, da XMLUnit im Standardstatus einen Tiefen-First-Ansatz zum Durchlaufen der XMLs verwendet und basierend auf der Dokumentreihenfolge die Knoten abgleichen. Daher wird<int> mit<boolean> abgeglichen.

Lassen Sie uns unseren Test so optimieren, dass er erfolgreich ist:

@Test
public void given2XMLS_whenSimilar_thenCorrect() {
    String controlXml = "3false";
    String testXml = "false3";

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

In diesem Fall teilen wirDefaultNodeMatcher mit, dass Sie, wenn XMLUnit nach einem zu vergleichenden Knoten fragt, die Knoten bereits nach ihren Elementnamen sortiert und abgeglichen haben sollten.

Das anfänglich fehlgeschlagene Beispiel ähnelte der Übergabe vonElementSelectors.Default anDefaultNodeMatcher.

Alternativ hätten wirDiff vonxmlunit-core verwenden können, anstattxmlunit-matchers zu verwenden:

@Test
public void given2XMLs_whenSimilarWithDiff_thenCorrect() throws Exception {
    String myControlXML = "3false";
    String myTestXML = "false3";
    Diff myDiffSimilar = DiffBuilder.compare(myControlXML).withTest(myTestXML)
      .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName))
      .checkForSimilar().build();

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

6. BenutzerdefinierteDifferenceEvaluator

ADifferenceEvaluator bestimmt das Ergebnis eines Vergleichs. Ihre Rolle beschränkt sich darauf, die Schwere des Vergleichsergebnisses zu bestimmen.

Es ist die Klasse, die entscheidet, ob zwei XML-Teileidentical,similar oderdifferent sind.

Betrachten Sie die folgenden XML-Teile:

and:

Im Standardzustand werden sie technisch als unterschiedlich bewertet, da ihreattr-Attribute unterschiedliche Werte haben. Schauen wir uns einen Test an:

@Test
public void given2XMLsWithDifferences_whenTestsDifferentWithoutDifferenceEvaluator_thenCorrect(){
    final String control = "";
    final String test = "";
    Diff myDiff = DiffBuilder.compare(control).withTest(test)
      .checkForSimilar().build();
    assertFalse(myDiff.toString(), myDiff.hasDifferences());
}

Fehlermeldung:

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

Wenn uns das Attribut nicht wirklich wichtig ist, können wir das Verhalten vonDifferenceEvaluator ändern, um es zu ignorieren. Wir tun dies, indem wir unsere eigenen erstellen:

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;
    }
}

Wir schreiben dann unseren ersten fehlgeschlagenen Test neu und liefern unsere eigeneDifferenceEvaluator-Instanz wie folgt:

@Test
public void given2XMLsWithDifferences_whenTestsSimilarWithDifferenceEvaluator_thenCorrect() {
    final String control = "";
    final String test = "";
    Diff myDiff = DiffBuilder.compare(control).withTest(test)
      .withDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr"))
      .checkForSimilar().build();

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

Diesmal geht es vorbei.

7. Validierung

XMLUnit führt eine XML-Validierung mit der KlasseValidatordurch. Sie erstellen eine Instanz davon mit der Factory-MethodeforLanguage, während Sie das Schema übergeben, das für die Validierung verwendet werden soll.

Das Schema wird als URI übergeben, der zu seinem Speicherort führt. XMLUnit abstrahiert die unterstützten Schemapositionen in der KlasseLanguagesals Konstanten.

Wir erstellen normalerweise eine Instanz der KlasseValidatorwie folgt:

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

Wenn wir nach diesem Schritt eine eigene XSD-Datei zur Validierung anhand unseres XML haben, geben wir einfach die Quelle an und rufen dann dieValidator-MethodevalidateInstancemit unserer XML-Dateiquelle auf.

Nehmen wir zum Beispiel unserestudents.xsd:



    
        
            
                
            
        
    
    
        
            
            
        
        
    

Undstudents.xml:



    
        Rajiv
        18
    
    
        Candie
        19
    

Führen wir dann einen Test durch:

@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 probs = r.getProblems().iterator();
    while (probs.hasNext()) {
        probs.next().toString();
    }
    assertTrue(r.isValid());
}

Das Ergebnis der Validierung ist eine Instanz vonValidationResult, die ein boolesches Flag enthält, das angibt, ob das Dokument erfolgreich validiert wurde.

ValidationResult enthält auchIterable mitValidationProblems, falls ein Fehler auftritt. Erstellen wir ein neues XML mit Fehlern namensstudents_with_error.xml.. Anstelle von<student> sind unsere Start-Tags alle</studet>:



    
        Rajiv
        18
    
    
        Candie
        19
    

Führen Sie dann diesen Test dagegen aus:

@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 probs = r.getProblems().iterator();
    int count = 0;
    while (probs.hasNext()) {
        count++;
        probs.next().toString();
    }
    assertTrue(count > 0);
}

Wenn wir die Fehler in derwhile-Schleife drucken würden, würden sie wie folgt aussehen:

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 "".' }
ValidationProblem { line=6, column=4, type=ERROR, message='The element type "studet"
  must be terminated by the matching end-tag "".' }

8. XPath

Wenn ein XPath-Ausdruck anhand eines XML-Teils ausgewertet wird, wird einNodeList erstellt, das die übereinstimmendenNodes. enthält

Betrachten Sie dieses XML-Teil, das in einer Datei namensteachers.xml gespeichert ist:


    
        math
        physics
    
    
        political education
        english
    

XMLUnit bietet eine Reihe von XPath-bezogenen Assertionsmethoden (siehe unten).

Wir können alle Knoten namensteacher abrufen und Assertions für sie einzeln ausführen:

@Test
public void givenXPath_whenAbleToRetrieveNodes_thenCorrect() {
    Iterable i = new JAXPXPathEngine()
      .selectNodes("//teacher", Input.fromFile(new File("teachers.xml")).build());
    assertNotNull(i);
    int count = 0;
    for (Iterator 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);
}

Beachten Sie, wie wir die Anzahl der untergeordneten Knoten, den Namen jedes Knotens und die Attribute in jedem Knoten überprüfen. Nach dem Abrufen derNode stehen viele weitere Optionen zur Verfügung.

Um zu überprüfen, ob ein Pfad existiert, können wir Folgendes tun:

@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"));
}

So stellen Sie sicher, dass kein Pfad vorhanden ist:

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

XPaths sind besonders nützlich, wenn ein Dokument hauptsächlich aus bekannten, unveränderlichen Inhalten besteht und nur wenige vom System erstellte Inhalte geändert werden.

9. Fazit

In diesem Tutorial haben wir die meisten Grundfunktionen vonXMLUnit 2.x vorgestellt und erläutert, wie Sie sie zum Validieren von XML-Dokumenten in unseren Anwendungen verwenden können.

Die vollständige Implementierung all dieser Beispiele und Codefragmente finden Sie inXMLUnitGitHub project.