Сравните две строки XML, игнорируя порядок элементов
Предположим, у меня есть две строки XML
<test>
<elem>a</elem>
<elem>b</elem>
</test>
<test>
<elem>b</elem>
<elem>a</elem>
</test>
Как написать тест, который сравнивает эти две строки и игнорирует порядок элементов?
Я хочу, чтобы тест был максимально коротким, не было места для 10-строчного анализа XML и т.д. Я ищу простое утверждение или что-то подобное.
У меня есть это (что не работает)
Diff diff = XMLUnit.compareXML(expectedString, actualString);
XMLAssert.assertXMLEqual("meh", diff, true);
Ответы
Ответ 1
Мой оригинальный ответ устарел. Если бы мне пришлось построить его снова, я бы использовал xmlunit 2 и xmlunit-matchers. Обратите внимание, что для модуля xml другой порядок всегда "похож", а не равен.
@Test
public void testXmlUnit() {
String myControlXML = "<test><elem>a</elem><elem>b</elem></test>";
String expected = "<test><elem>b</elem><elem>a</elem></test>";
assertThat(myControlXML, isSimilarTo(expected)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)));
//In case you wan't to ignore whitespaces add ignoreWhitespace().normalizeWhitespace()
assertThat(myControlXML, isSimilarTo(expected)
.ignoreWhitespace()
.normalizeWhitespace()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)));
}
Если кто-то все еще не хочет использовать чистую реализацию Java, вот она. Эта реализация извлекает содержимое из xml и сравнивает список, игнорируя порядок.
public static Document loadXMLFromString(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(xml));
return builder.parse(is);
}
@Test
public void test() throws Exception {
Document doc = loadXMLFromString("<test>\n" +
" <elem>b</elem>\n" +
" <elem>a</elem>\n" +
"</test>");
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("//test//elem");
NodeList all = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
List<String> values = new ArrayList<>();
if (all != null && all.getLength() > 0) {
for (int i = 0; i < all.getLength(); i++) {
values.add(all.item(i).getTextContent());
}
}
Set<String> expected = new HashSet<>(Arrays.asList("a", "b"));
assertThat("List equality without order",
values, containsInAnyOrder(expected.toArray()));
}
Ответ 2
XMLUnit будет делать то, что вы хотите, но вы должны указать elementQualifier. Если спецификатор elementQualifier не указан, он будет сравнивать только узлы в той же позиции.
Для вашего примера вы хотите ElementNameAndTextQualifer, он считает, что node аналогичен, если существует, который соответствует имени элемента и его текстовому значению, например:
Diff diff = new Diff(control, toTest);
// we don't care about ordering
diff.overrideElementQualifier(new ElementNameAndTextQualifier());
XMLAssert.assertXMLEqual(diff, true);
Подробнее об этом можно прочитать здесь: http://xmlunit.sourceforge.net/userguide/html/ar01s03.html#ElementQualifier
Ответ 3
Для xmlunit 2.0 (я искал это) теперь это делается с помощью DefaultNodeMatcher
Diff diff = Diffbuilder.compare(Input.fromFile(control))
.withTest(Input.fromFile(test))
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.build()
Надеюсь, что это помогает, это помогает другим людям гуглить...
Ответ 4
Перекрестная проводка из Сравнение XML, игнорирующего порядок дочерних элементов
У меня была такая же потребность сегодня вечером, и я не мог найти то, что соответствовало моим требованиям.
Мое обходное решение состояло в сортировке двух файлов XML, которые я хотел разделить, сортируя по алфавиту по имени элемента. Как только они были в одинаковом порядке, я мог бы разграничить два отсортированных файла, используя обычный инструмент визуального разграничения.
Если этот подход полезен для кого-то еще, я поделился с python script, который я написал, чтобы сортировать по http://dalelane.co.uk/blog/?p=3225
Ответ 5
ВАРИАНТ 1
Если XML-код прост, попробуйте следующее:
String testString = ...
assertTrue(testString.matches("(?m)^<test>(\\s*<elem>(a|b)</elem>\\s*){2}</test>$"));
ВАРИАНТ 2
Если XML более сложный, загрузите его парсером XML и сравните фактические узлы, найденные с вами ссылочными узлами.
Ответ 6
Как пример того, как сравнивать более сложные сочетания xml-элементов на основе равенства атрибута name
. Например:
<request>
<param name="foo" style="" type="xs:int"/>
<param name="Cookie" path="cookie" style="header" type="xs:string" />
</request>
против.
<request>
<param name="Cookie" path="cookie" style="header" type="xs:string" />
<param name="foo" style="query" type="xs:int"/>
</request>
Со следующим спецификатором пользовательских элементов:
final Diff diff = XMLUnit.compareXML(controlXml, testXml);
diff.overrideElementQualifier(new ElementNameAndTextQualifier() {
@Override
public boolean qualifyForComparison(final Element control, final Element test) {
// this condition is copied from super.super class
if (!(control != null && test != null
&& equalsNamespace(control, test)
&& getNonNamespacedNodeName(control).equals(getNonNamespacedNodeName(test)))) {
return false;
}
// matching based on 'name' attribute
if (control.hasAttribute("name") && test.hasAttribute("name")) {
if (control.getAttribute("name").equals(test.getAttribute("name"))) {
return true;
}
}
return false;
}
});
XMLAssert.assertXMLEqual(diff, true);
Ответ 7
Для меня мне также необходимо добавить метод checkForSimilar()
в DiffBuilder
.
Без него утверждение было ошибочным, говоря, что последовательность узлов была не одинаковой (позиция в дочернем списке не была одинаковой)
Мой код:
Diff diff = Diffbuilder.compare(Input.fromFile(control))
.withTest(Input.fromFile(test))
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.checkForSimilar()
.build()