Учебник XmlUnit с примерами

Изучите XmlUnit 2.x для тестирования и проверки XML-документов с помощью простых для понимания примеров. XmlUnit требует от нас точного знания XML-контента, который мы хотим проверить, сравнивая его с заданным тестовым XML.

Для утверждений мы используем библиотеку Hamcrest matchers.

1. Зависимость Maven

Включите в проект последнюю версию зависимостей xmlunit-core и xmlunit-matchers.

<dependency><groupId>org.xmlunit</groupId><artifactId>xmlunit-core</artifactId><version>2.9.0</version></dependency><dependency><groupId>org.xmlunit</groupId><artifactId>xmlunit-matchers</artifactId><version>2.9.0</version></dependency><!-- If using Hamcrest matchers --><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest</artifactId><version>2.2</version></dependency>

2. Чтение и сравнение XML-источников

2.1 Чтение XML-источников

Для проведения сравнений CompareMatcher XmlUnit может принимать входные данные из различных источников, таких как:

  • java.яз.Строка
  • java.io.Файл
  • java.io.InputStream
  • java.io.Читатель
  • массив байтов
  • org.w3c.dom.Document и т. д.

Следующий код демонстрирует чтение файла widget.xml различными способами.

@Testvoid createSource_ThenSuccess() throws ParserConfigurationException, IOException, SAXException {//1 - Using FileInput.from(new File("widget.xml"));Input.fromFile("widget.xml");Input.fromFile(getClass().getClassLoader().getResource("widget.xml").getPath());//2 - Using StringInput.from("<widget><id>1</id><name>demo</name></widget>");Input.fromString("<widget><id>1</id><name>demo</name></widget>");//3 - Using StreamInput.from(XmlUnitExamples.class.getResourceAsStream("widget.xml"));Input.fromStream(XmlUnitExamples.class.getResourceAsStream("widget.xml"));//4 - From ReaderInput.from(new StringReader("<widget><id>1</id><name>demo</name></widget>"));Input.fromReader(new StringReader("<widget><id>1</id><name>demo</name></widget>"));//5 - From Byte ArrayInput.from("<widget><id>1</id><name>demo</name></widget>".getBytes());Input.fromByteArray("<widget><id>1</id><name>demo</name></widget>".getBytes());//6 - From DocumentDocumentBuilder b = DocumentBuilderFactory.newInstance().newDocumentBuilder();Document document = b.parse(new File("widget.xml"));Input.from(document);Input.fromDocument(document);}

2.2 Сравнение XML-источников

Мы можем сравнивать два XML разными способами. В следующем примере мы сравниваем две XML-строки.

String testXml = "<widget><id>1</id><name>demo</name></widget>";String controlXml = "<widget><id>1</id><name>demo</name></widget>";assertThat(testXml, isIdenticalTo(identicalXml));

Аналогично, в следующем примере мы сравниваем два XML-файла.

assertThat(new File("widget.xml"), isIdenticalTo(identicalXml));assertThat(new File("widget.xml"), isIdenticalTo(new File("otherFile.xml")));

При сравнении мы можем смешивать различные типы источников.

assertThat(new File("widget.xml"), isIdenticalTo(Input.from(identicalXml)));

3. Сравнительные примеры с использованием XmlUnit

Давайте рассмотрим различные способы сравнения двух XML-файлов с помощью XmlUnit.

3.1 Сравнение идентичных XML-файлов

Два XML-файла считаются идентичными, если они имеют одинаковое содержимое, т. е. оба XML-файла имеют одинаковые теги, в одинаковом порядке и с одинаковыми значениями.

В следующем примере статические методы CompareMatcher используются для сравнения двух XML-файлов.

@Testvoid compareIdenticalAndSimilarXmlWithHamcrest_ThenSuccess() throws Exception {String testXml = "<widget><id>1</id><name>demo</name></widget>";String identicalXml = "<widget><id>1</id><name>demo</name></widget>";String nonIdenticalXml = "<widget><name>demo</name><id>1</id></widget>";assertThat(testXml, isIdenticalTo(identicalXml));assertThat(testXml, not(isIdenticalTo(nonIdenticalXml)));}

Мы также можем использовать API DiffBuilder, который собирает все различия между двумя XML. DiffBuilder — это гибкий API для получения различий более контролируемым образом.

@Testvoid compareIdenticalAndSimilarXmlDiff_ThenSuccess() throws Exception {String testXml = "<widget><id>1</id><name>demo</name></widget>";String identicalXml = "<widget><id>1</id><name>demo</name></widget>";Diff diffForIdentical = DiffBuilder.compare(testXml).withTest(identicalXml).checkForIdentical().build();assertThat(IterableUtils.size(diffForIdentical.getDifferences()), equalTo(0));}

3.2 Сравнение похожих XML-файлов

Два XML считаются похожими, если у них одинаковые теги и значения, но порядок тегов разный. В следующем примере nonIdenticalXml имеет похожие теги, но порядок id и name разный.

По умолчанию XmlUnit использует DefaultNodeMatcher, который сопоставляет теги XML на том же уровне глубины от корневого тега и в том же порядке. Чтобы попросить XmlUnit игнорировать порядок, мы используем ElementSelectors.byName, который сортирует все теги по имени на той же глубине, прежде чем начать их сопоставлять.

@Testvoid compareIdenticalAndSimilarXmlWithHamcrest_ThenSuccess() throws Exception {String testXml = "<widget><id>1</id><name>demo</name></widget>";String nonIdenticalXml = "<widget><name>demo</name><id>1</id></widget>";assertThat(testXml, isSimilarTo(nonIdenticalXml).withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName)));}

Аналогично мы можем использовать ElementSelectors.byName с API DiffBuilder.

@Testvoid compareIdenticalAndSimilarXmlDiff_ThenSuccess() throws Exception {String testXml = "<widget><id>1</id><name>demo</name></widget>";String nonIdenticalXml = "<widget><name>demo</name><id>1</id></widget>";Diff diffForSimilarity = DiffBuilder.compare(testXml).withTest(nonIdenticalXml).checkForSimilar().withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName)).build();assertThat(IterableUtils.size(diffForSimilarity.getDifferences()), equalTo(0));}

3.3 Сравнение или игнорирование определенных тегов

Может быть требование игнорировать определенные теги или сравнивать только несколько определенных тегов при сопоставлении XML. Мы можем добиться этого с помощью метода withNodeFilter(). Этот метод будет вызван для каждого узла в XML. Если метод возвращает true, то сравнение будет выполнено, в противном случае пропускается.

В следующих XML-файлах будет сравниваться только тег id. Тег name будет пропущен. Мы можем включить или исключить любое количество тегов, создав сложное выражение внутри метода withNodeFilter().

@Testvoid compareOnlySpecificTags_ThenSuccess() {String testXml = "<widget><id>1</id><name>test</name></widget>";String otherXml = "<widget><name>live</name><id>1</id></widget>";Diff diffs = DiffBuilder.compare(testXml).withTest(otherXml).withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName)).withNodeFilter(node -> node.getNodeName().equalsIgnoreCase("id")).build();assertThat(IterableUtils.size(diffs.getDifferences()), equalTo(0));}

3.4 Сравнение с использованием XPath

Сравнение всего XML не осуществимо во всех ситуациях. Мы можем использовать выражения XPath, если хотим сравнить только определенный раздел XML.

@Testvoid containsAsChildXmlUsingXPath_ThenSuccess() throws Exception {String fullXml = "<widget><id>1</id><name>demo</name></widget>";//Contains 'id' tagassertThat(fullXml, HasXPathMatcher.hasXPath("/widget/id"));//Compare value of 'id' tagassertThat(fullXml, EvaluateXPathMatcher.hasXPath("/widget/id/text()", equalTo("1")));}

4. Нормализация XML-файлов

Полученный XML не всегда может быть нормализованным и чистым. Мы можем нормализовать XML, используя два подхода:

4.1 Использование исходных декораторов

XmlUnit предоставляет следующие декораторы, которые мы можем использовать при сборке экземпляра Source в первом разделе.

  • CommentLessSource: предоставляет XML, состоящий из содержимого исходного источника, из которого удалены все комментарии.
  • WhitespaceStrippedSource: удаляет все пустые текстовые узлы и обрезает оставшиеся текстовые узлы. Фактически, он удаляет все пустые строки из XML, включая содержимое узла.
  • WhitespaceNormalizedSource: заменяет все пробельные символы, обнаруженные в текстовых узлах, на пробелы и объединяет последовательные пробельные символы в один пробел.
  • NormalizedSource: смежные текстовые узлы объединяются в отдельные узлы, а пустые текстовые узлы удаляются(рекурсивно).
  • ElementContentWhitespaceStrippedSource: удаляет все текстовые узлы, состоящие исключительно из пробелов. Удаляет все «пробелы содержимого элементов», т.е. текстовое содержимое между элементами XML, которое является просто артефактом «красивой печати» XML.
@Testvoid normalizedXmlSources_ThenSuccess() throws Exception {CommentLessSource commentLessSource= new CommentLessSource(Input.fromFile("widget.xml").build());WhitespaceStrippedSource whitespaceStrippedSource= new WhitespaceStrippedSource(Input.fromFile("widget.xml").build());WhitespaceNormalizedSource whitespaceNormalizedSource= new WhitespaceNormalizedSource(Input.fromFile("widget.xml").build());ElementContentWhitespaceStrippedSource elementContentWhitespaceStrippedSource= new ElementContentWhitespaceStrippedSource(Input.fromFile("widget.xml").build());}

4.2 Использование CompareMatcher

Другой способ нормализации XML-файлов — использование фабричных методов, предоставляемых классом CompareMatcher, при сравнении обоих XML-содержимых.

@Testvoid normalizedXmlMatchers_ThenSuccess() throws Exception {String testXml = "<widget><id>1</id><name>demo</name></widget>";String identicalXml = "<widget><id>1</id><name>demo</name></widget>";assertThat(testXml, isIdenticalTo(identicalXml).normalizeWhitespace());assertThat(testXml, isIdenticalTo(identicalXml).ignoreComments());}

5. Заключение

В этом уроке по XmlUnit 2.x мы научились создавать различные типы источников XML, нормализовать их с помощью декораторов и сравнивать на предмет идентичности, схожести и фрагментов с помощью XPath.

Исходный код на Github

Прокрутить вверх