Изучите 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.