2つのxml文字列があるとします
<test>
<elem>a</elem>
<elem>b</elem>
</test>
<test>
<elem>b</elem>
<elem>a</elem>
</test>
これらの2つの文字列を比較し、要素の順序を無視するテストを作成する方法は?
テストをできるだけ短くし、10行のXML解析などを行う場所がないようにしたいのですが、単純なアサーションまたは類似のものを探しています。
私はこれを持っています(動作しません)
Diff diff = XMLUnit.compareXML(expectedString, actualString);
XMLAssert.assertXMLEqual("meh", diff, true);
私の元の答えは時代遅れです。もう一度ビルドする必要がある場合は、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()));
}
Xmlunit 2.0(これを探していました)の場合、 DefaultNodeMatcher を使用して実行されます
Diff diff = Diffbuilder.compare(Input.fromFile(control))
.withTest(Input.fromFile(test))
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.build()
これが他の人がグーグルするのに役立つことを願っています...
XMLUnitは必要なことを行いますが、elementQualifierを指定する必要があります。 elementQualifierが指定されていない場合、同じ位置にあるノードのみが比較されます。
ElementNameAndTextQualiferが必要な例では、次のような要素名とテキスト値に一致するノードが存在する場合、これは類似したノードと見なされます。
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
からのクロスポスト 子要素の順序を無視してXMLを比較
今晩も同様のニーズがあり、自分の要件に合うものを見つけることができませんでした。
私の回避策は、比較したい2つのXMLファイルを要素名でアルファベット順に並べ替えることでした。両方が一貫した順序になったら、通常の視覚的差分ツールを使用して、2つのソートされたファイルを比較できます。
このアプローチが他のユーザーにとって有用であると思われる場合は、 http://dalelane.co.uk/blog/?pでソートを行うために書いたpythonスクリプトを共有しました= 3225
オプション1
XMLコードが単純な場合は、次のことを試してください。
String testString = ...
assertTrue(testString.matches("(?m)^<test>(\\s*<elem>(a|b)</elem>\\s*){2}</test>$"));
オプション2
XMLがさらに複雑な場合は、XMLパーサーを使用してロードし、参照ノードと実際のノードを比較します。
私にとっては、DiffBuilder
にメソッドcheckForSimilar()
も追加する必要がありました。これがないと、ノードのシーケンスが同じではない(子リスト内の位置が同じではなかった)とアサートは誤って言っていました
私のコードは:
Diff diff = Diffbuilder.compare(Input.fromFile(control))
.withTest(Input.fromFile(test))
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.checkForSimilar()
.build()
属性name
の等価性に基づいて一致するより複雑なxml要素を比較する方法の例と同じです。例えば:
<request>
<param name="foo" style="" type="xs:int"/>
<param name="Cookie" path="cookie" style="header" type="xs:string" />
</request>
vs.
<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);