Java >> Java tutorial >  >> Tag >> XML

Introduktion til XMLUnit 2.x

1. Oversigt

XMLUnit 2.x er et kraftfuldt bibliotek, der hjælper os med at teste og verificere XML-indhold, og det er særligt praktisk, når vi ved præcis, hvad den XML skal indeholde.

Så vi vil hovedsageligt bruge XMLUnit inde i enhedstest for at bekræfte, at det, vi har, er gyldig XML , at det indeholder visse oplysninger eller er i overensstemmelse med et bestemt stildokument.

Derudover, med XMLUnit, har vi kontrol over, hvilken slags forskel der er vigtig for os og hvilken del af stilhenvisningen, der skal sammenlignes med hvilken del af din sammenligning XML.

Da vi fokuserer på XMLUnit 2.x og ikke XMLUnit 1.x, når vi bruger ordet XMLUnit, henviser vi strengt til 2.x.

Endelig vil vi også bruge Hamcrest-matchere til påstande, så det er en god idé at friske op på Hamcrest, hvis du ikke er bekendt med det.

2. XMLUnit Maven-opsætning

For at bruge biblioteket i vores maven-projekter skal vi have følgende afhængigheder i pom.xml :

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

Den seneste version af xmlunit-core kan findes ved at følge dette link. Og:

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

Den seneste version af xmlunit-matchers er tilgængelig på dette link.

3. Sammenligning af XML

3.1. Simple forskelseksempler

Lad os antage, at vi har to stykker XML. De anses for at være identiske, når indholdet og rækkefølgen af ​​noderne i dokumenterne er nøjagtig det samme, så følgende test vil bestå:

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

Denne næste test mislykkes, da de to stykker XML er ens, men ikke identiske, da deres noder forekommer i en anden rækkefølge :

@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. Detaljeret forskelseksempel

Forskelle mellem to XML-dokumenter ovenfor registreres af Difference Engine .

Som standard og af effektivitetsmæssige årsager stopper den sammenligningsprocessen, så snart den første forskel er fundet.

For at få alle forskellene mellem to stykker XML bruger vi en forekomst af Diff klasse som sådan:

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

Hvis vi udskriver værdierne returneret i mens loop, er resultatet som nedenfor:

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)

Hver forekomst beskriver både typen af ​​forskel, der er fundet mellem en kontrolknude og testknude, og detaljerne i disse knudepunkter (inklusive XPath-placeringen af ​​hver knude).

Hvis vi vil tvinge Difference Engine til at stoppe efter den første forskel er fundet og ikke fortsætte med at opregne yderligere forskelle – vi skal levere en 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));
}

Forskellens budskab er enklere:

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

4. Inputkilder

Med XMLUnit, vi kan vælge XML-data fra en række forskellige kilder, der kan være praktiske i forhold til vores applikations behov. I dette tilfælde bruger vi Input klasse med dens række af statiske metoder.

For at vælge input fra en XML-fil, der er placeret i projektets rod, gør vi følgende:

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

For at vælge en inputkilde fra en XML-streng, som sådan:

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

Lad os nu bruge en stream som input:

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

Vi kunne også bruge Input.from(Object) hvor vi indgiver enhver gyldig kilde, der skal løses af XMLUnit.

For eksempel kan vi sende en fil i:

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

Eller en streng:

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

Eller en Strøm:

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

og de vil alle blive løst.

5. Sammenligning af specifikke noder

I afsnit 2 ovenfor så vi kun på identisk XML, fordi lignende XML kræver en lille smule tilpasning ved hjælp af funktioner fra xmlunit-core bibliotek:

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

Ovenstående test bør bestå, da XML'erne har lignende noder, men den mislykkes. Dette skyldes, at XMLUnit sammenligner kontrol- og testnoder i samme dybde i forhold til rodknuden .

Så en isSimilarTo tilstand er en smule mere interessant at teste end en isIdenticalTo tilstand. Noden 3 i controlXml vil blive sammenlignet med false i testXml , giver automatisk fejlmeddelelse:

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>

Det er her DefaultNodeMatcher og ElementSelector klasser af XMLUnit er nyttige

DefaultNodeMatcher klasse konsulteres af XMLUnit på sammenligningsstadiet, da den går over noder i controlXml, for at bestemme hvilken XML-node fra testXml at sammenligne med den aktuelle XML-node, den støder på i controlXml .

Før det, DefaultNodeMatcher vil allerede have konsulteret ElementSelector for at bestemme, hvordan noder skal matches.

Vores test mislykkedes, fordi XMLUnit i standardtilstanden vil bruge en dybde-først tilgang til at krydse XML'erne og baseret på dokumentrækkefølgen for at matche noder, derfor er matchet med .

Lad os tilpasse vores test, så den består:

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

I dette tilfælde fortæller vi DefaultNodeMatcher at når XMLUnit beder om en node at sammenligne, burde du allerede have sorteret og matchet noderne efter deres elementnavne.

Det oprindelige mislykkede eksempel lignede at bestå ElementSelectors.Default til DefaultNodeMatcher .

Alternativt kunne vi have brugt en Diff fra xmlunit-core i stedet for at bruge xmlunit-matchers :

@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. Tilpasset DifferenceEvaluator

En DifferenceEvaluator træffer afgørelser om resultatet af en sammenligning. Dens rolle er begrænset til at bestemme alvoren af ​​en sammenlignings resultat.

Det er klassen, der afgør, om to XML-stykker er identiske , lignende eller anderledes .

Overvej følgende XML-stykker:

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

og:

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

I standardtilstanden vurderes de teknisk som forskellige, fordi deres attr attributter har forskellige værdier. Lad os tage et kig på en test:

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

Fejlmeddelelse:

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

Hvis vi er ligeglade med attributten, kan vi ændre adfærden for DifferenceEvaluator at ignorere det. Det gør vi ved at skabe vores eget:

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

Vi omskriver derefter vores første mislykkede test og leverer vores egen DifferenceEvaluator eksempel, som sådan:

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

Denne gang går det.

7. Validering

XMLUnit udfører XML-validering ved hjælp af Validator klasse. Du opretter en forekomst af det ved hjælp af forLanguage fabriksmetode, mens du sender skemaet til brug ved validering.

Skemaet sendes ind som en URI, der fører til dets placering, XMLUnit abstraherer de skemaplaceringer, det understøtter i sprogene klasse som konstanter.

Vi opretter typisk en forekomst af Validator klasse som sådan:

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

Efter dette trin, hvis vi har vores egen XSD-fil til at validere mod vores XML, angiver vi blot dens kilde og kalder derefter Validator 's validateInstance metode med vores XML-filkilde.

Tag for eksempel vores 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>

Og 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>

Lad os derefter køre en test:

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

Resultatet af valideringen er en forekomst af ValidationResult som indeholder et boolesk flag, der angiver, om dokumentet er blevet valideret.

Valideringsresultatet indeholder også en Iterable med ValidationProblem s i tilfælde af en fejl. Lad os oprette en ny XML med fejl kaldet students_with_error.xml. I stedet for , vores starttags er alle :

<?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>

Kør derefter denne test mod det:

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

Hvis vi skulle udskrive fejlene i mens loop, ville de se ud som:

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

Når et XPath-udtryk evalueres mod et stykke XML, er en NodeList er oprettet, der indeholder de matchende Noder.

Overvej dette stykke XML gemt i en fil kaldet 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 tilbyder en række XPath-relaterede påstandsmetoder, som vist nedenfor.

Vi kan hente alle noderne kaldet lærer og udføre påstande om dem individuelt:

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

Bemærk, hvordan vi validerer antallet af underordnede noder, navnet på hver node og attributterne i hver node. Mange flere muligheder er tilgængelige efter at have hentet Noden .

For at bekræfte, at der findes en sti, kan vi gøre følgende:

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

For at bekræfte, at en sti ikke eksisterer, er dette, hvad vi kan gøre:

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

XPaths er især nyttige, hvor et dokument hovedsageligt består af kendt, uforanderligt indhold med kun en lille mængde ændrende indhold, der er oprettet af systemet.

9. Konklusion

I dette selvstudie har vi introduceret de fleste af de grundlæggende funktioner i XMLUnit 2.x og hvordan man bruger dem til at validere XML-dokumenter i vores applikationer.

Den fulde implementering af alle disse eksempler og kodestykker kan findes i XMLUnit GitHub-projekt.


Java tag