XMLUnit 2.xの紹介
1概要
XMLUnit 2.x
は、XMLコンテンツのテストと検証に役立つ強力なライブラリであり、XMLに何が含まれるべきかを正確に知っているときに特に役立ちます。
したがって、主に単体テスト
の中でXMLUnitを使用して、有効なXML
であることを確認し、特定の情報が含まれているか、特定のスタイル文書に準拠していることを確認します。
さらに、XMLUnitを使用すると、どのような違いが重要か、またスタイル参照のどの部分を比較XMLのどの部分と比較するかを制御できます。
XMLUnit 1.xではなくXMLUnit 2.xに焦点を合わせているので、XMLUnitという単語を使用するときはいつでも、2.xを厳密に参照しています。
最後に、私たちはアサーションにもHamcrestのマッチャーを使用しますので、リンクをブラッシュアップすることをお勧めします。
2 XMLUnit Mavenのセットアップ
私たちのMavenプロジェクトでこのライブラリを使用するには、
pom.xml
に次の依存関係が必要です。
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.2.1</version>
</dependency>
xmlunit-core
の最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22xmlunit-core%22[このリンク]をたどることで見つけることができます。そして:
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
<version>2.2.1</version>
</dependency>
xmlunit-matchers
の最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22xmlunit- matchers%22[このリンク]で入手できます。
3 XMLの比較
3.1. 単純な違いの例
XMLが2つあるとしましょう。ドキュメント内のノードの内容と順序がまったく同じであれば、それらは同一であると見なされるため、次のテストに合格します。
@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));
}
この次のテストは、2つのXMLが似ていますが、ノードが異なる順序で出現しているため同一ではないため、失敗します。
@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. 詳細な違いの例
上記の2つのXML文書の違いは
Difference Engine
によって検出されます。
デフォルトでは効率のために、最初の違いが見つかるとすぐに比較プロセスを停止します。
2つのXMLの間のすべての違いを得るために、
Diff
クラスのインスタンスを使います。
@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));
}
while
ループで返された値を出力すると、結果は以下のようになります。
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)
各インスタンスは、コントロールノードとテストノードの間に見られる違いのタイプとそれらのノードの詳細(各ノードのXPathロケーションを含む)の両方を記述します。
最初の違いが見つかった後でDifference Engineを** 停止させ、それ以上の違いを列挙しない場合 –
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));
}
-
違いのメッセージはより単純です:**
Expected element tag name 'int' but was 'boolean' -
comparing <int...> at/struct[1]/int[1]
to <boolean...> at/struct[1]/boolean[1](DIFFERENT)
** 4入力ソース
XMLUnitを使用すると、アプリケーションのニーズに合わせてさまざまなソースからXMLデータを選択できます。この場合、
Input
クラスとその静的メソッドの配列を使用します。
プロジェクトルートにあるXMLファイルから入力を選択するには、次のようにします。
@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)));
}
XML文字列から入力ソースを選ぶには、次のようにします。
@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)));
}
入力としてストリームを使用しましょう。
@Test
public void givenStreamAsSource__whenAbleToInput__thenCorrect() {
assertThat(Input.fromStream(XMLUnitTests.class
.getResourceAsStream("/test.xml")),
isSimilarTo(
Input.fromStream(XMLUnitTests.class
.getResourceAsStream("/control.xml"))));
}
XMLUnitによって解決される有効なソースを渡す場合は、
Input.from(Object)
を使用することもできます。
たとえば、ファイルを次の場所に渡すことができます。
@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()))));
}
または
String:
@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>")));
}
または
Stream:
@Test
public void givenStreamAsObject__whenAbleToInput__thenCorrect() {
assertThat(
Input.from(XMLUnitTest.class.getResourceAsStream("/test.xml")),
isSimilarTo(Input.from(XMLUnitTest.class.getResourceAsStream("/control.xml"))));
}
そしてそれらはすべて解決されます。
5特定のノードを比較する
上記のセクション2では、
xmlunit-core
libraryの機能を使用して、類似のXMLには少しカスタマイズが必要なため、同一のXMLのみを調べました。
@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));
}
XMLには似たようなノードがあるので上記のテストは成功するはずですが、失敗します。これは、
XMLUnitが、ルートノード
に対して同じ深さでコントロールノードとテストノードを比較するためです。
そのため、
isSimilarTo
条件は、
isIdenticalTo
条件よりもテストが少し興味深いものです。
controlXml
のノード
<int> 3 </int>
は、
testXml
の
<boolean> false </boolean>
と比較され、自動的に失敗メッセージが表示されます。
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>
XMLUnitの
DefaultNodeMatcher
クラスと
ElementSelector
クラスが役に立つのはここです
DefaultNodeMatcher
クラスは、
controlXml、
のノードをループするときに比較段階でXMLUnitに問い合わせられ、
testXml
のどのXMLノードが
controlXml
で検出された現在のXMLノードと比較されるかを決定します。
その前に、
DefaultNodeMatcher
は既に
ElementSelector
を調べてノードのマッチング方法を決定しています。
我々のテストは失敗した。デフォルトの状態では、XMLUnitはXMLをトラバースするためにそしてドキュメントの順序に基づいてノードを一致させるために深さ優先のアプローチを使用するので、
<int>
は
<boolean>
と一致する。
合格するようにテストを調整しましょう。
@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)));
}
この場合、XMLUnitが比較するノードを要求したときには、既にそれらの要素名でノードをソートして一致させる必要があることを
DefaultNodeMatcher
に伝えています。
最初に失敗した例は、
ElementSelectors.Default
を
DefaultNodeMatcher
に渡すのと似ていました。
代わりに、
xmlunit-matchers
を使用するのではなく、
xmlunit-core
の
Diff
を使用することもできます。
@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. カスタム
DifferenceEvaluator
DifferenceEvaluator
は、比較の結果を決定します。その役割は、比較結果の重大度を決定することに限定されています。
これは、2つのXMLが
同一
、
類似
、または
差異
のどれであるかを決定するクラスです。
次のXML部分を検討してください。
<a>
<b attr="abc">
</b>
</a>
そして:
<a>
<b attr="xyz">
</b>
</a>
デフォルトの状態では、
attr
属性の値が異なるため、技術的に異なると評価されます。テストを見てみましょう。
@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());
}
失敗メッセージ
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
属性をあまり気にしないのであれば、
DifferenceEvaluator
の動作を変更して無視することができます。私達は私達の自身を作成することによってこれを行います。
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;
}
}
次に、最初の失敗したテストを書き換えて、次のように独自の
DifferenceEvaluator
インスタンスを指定します。
@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());
}
今回は合格です。
7. 検証
XMLUnitは、
Validator
クラスを使用してXML検証を実行します。検証に使用するスキーマを渡しながら、
forLanguage
factoryメソッドを使用してそのインスタンスを作成します。
スキーマはその場所へのURIとして渡され、XMLUnitは
Languages
クラスでサポートするスキーマの場所を定数として抽象化します。
通常、
Validator
クラスのインスタンスを次のように作成します。
Validator v = Validator.forLanguage(Languages.W3C__XML__SCHEMA__NS__URI);
この手順の後、XMLに対して検証するための独自のXSDファイルがある場合は、単にそのソースを指定してから、XMLファイルのソースを使用して
Validator
‘s svalidateInstanceメソッドを呼び出します。
たとえば
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>
そして
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>
それではテストを実行しましょう。
@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());
}
検証の結果は、文書が正常に検証されたかどうかを示すブールフラグを含む
ValidationResult
のインスタンスです。
失敗した場合に備えて、
ValidationResult
には
__ValidationProblem
sを含む
Iterable
も含まれています。
students
with
error.xmlというエラーのある新しいXMLを作成しましょう。
<student>
の代わりに、開始タグはすべて
</studet>
です。
<?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>
それからそれに対してこのテストを実行します。
@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);
}
while
ループのエラーを表示すると、エラーは次のようになります。
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
XPath式がXMLに対して評価されると、一致する
Nodes.
を含む
NodeList
が作成されます。
このXMLの一部を
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は、次に示すように、XPath関連のアサーションメソッドを多数提供しています。
teacher
という名前のすべてのノードを取得し、それらに対して個別にアサーションを実行できます。
@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);
}
子ノードの数、各ノードの名前、および各ノードの属性を検証する方法に注目してください。
Node
を取得した後、さらに多くのオプションが利用可能になります。
パスが存在することを確認するために、以下を実行できます。
@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"));
}
パスが存在しないことを確認するために、これが可能です。
@Test
public void givenXmlSource__whenFailsToValidateInExistentXPath__thenCorrect() {
assertThat(Input.fromFile(new File("teachers.xml")), not(hasXPath("//sujet")));
}
XPathは、システムによって作成されたわずかな量の変化するコンテンツのみを含む、ドキュメントの大部分が既知の不変のコンテンツで構成される場合に特に便利です。
9結論
このチュートリアルでは、
XMLUnit 2.x
の基本機能の大部分と、それらを使用してアプリケーションでXML文書を検証する方法を紹介しました。
これらすべての例とコードスニペットの完全な実装は、
XMLUnit
GitHubプロジェクト
にあります。