XMLUnit 2.xの概要
1. 概要
XMLUnit 2.xは、XMLコンテンツのテストと検証に役立つ強力なライブラリであり、そのXMLに何を含めるべきかを正確に把握している場合に特に便利です。
そのため、主に単体テストto verify that what we have is valid XML内でXMLUnitを使用します。これは、特定の情報が含まれているか、特定のスタイルのドキュメントに準拠しているためです。
さらに、XMLUnitを使用すると、we have control over what kind of difference is important to usと、スタイル参照のどの部分を比較XMLのどの部分と比較するかを指定できます。
XMLUnit 1.xではなくXMLUnit 2.xに焦点を合わせているため、XMLUnitという単語を使用するときは常に、2.xを厳密に参照しています。
最後に、アサーションにもHamcrestマッチャーを使用するため、Hamcrestに慣れていない場合は、ブラッシュアップすることをお勧めします。
2. XMLUnitMavenセットアップ
3. XMLの比較
3.1. 単純な違いの例
2つのXMLがあるとしましょう。 文書内のノードのコンテンツとシーケンスがまったく同じ場合、それらは同一であると見なされるため、次のテストに合格します。
@Test
public void given2XMLS_whenIdentical_thenCorrect() {
String controlXml = "3 false ";
String testXml = "3 false ";
assertThat(testXml, CompareMatcher.isIdenticalTo(controlXml));
}
この次のテストは、2つのXMLが類似しているが、nodes occur in a different sequenceと同一ではないため、失敗します。
@Test
public void given2XMLSWithSimilarNodesButDifferentSequence_whenNotIdentical_thenCorrect() {
String controlXml = "3 false ";
String testXml = "false 3 ";
assertThat(testXml, assertThat(testXml, not(isIdenticalTo(controlXml)));
}
3.2. 詳細な違いの例
上記の2つのXMLドキュメントの違いは、Difference Engineによって検出されます。
デフォルトおよび効率上の理由により、最初の違いが見つかるとすぐに比較プロセスを停止します。
2つのXMLの違いをすべて取得するには、次のようにDiffクラスのインスタンスを使用します。
@Test
public void given2XMLS_whenGeneratesDifferences_thenCorrect(){
String controlXml = "3 false ";
String testXml = "false 3 ";
Diff myDiff = DiffBuilder.compare(controlXml).withTest(testXml).build();
Iterator 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 at /struct[1]/int[1] to
at /struct[1]/boolean[1] (DIFFERENT)
Expected text value '3' but was 'false' -
comparing 3 at /struct[1]/int[1]/text()[1] to
false at /struct[1]/boolean[1]/text()[1] (DIFFERENT)
Expected element tag name 'boolean' but was 'int' -
comparing at /struct[1]/boolean[1]
to at /struct[1]/int[1] (DIFFERENT)
Expected text value 'false' but was '3' -
comparing false at /struct[1]/boolean[1]/text()[1]
to 3 at /struct[1]/int[1]/text()[1] (DIFFERENT)
各インスタンスは、コントロールノードとテストノードの間で見つかった違いのタイプと、それらのノードの詳細(各ノードのXPathロケーションを含む)の両方を記述します。
階差機関をstop after the first difference is foundに強制し、それ以上の差異の列挙に進まない場合は、ComparisonControllerを指定する必要があります。
@Test
public void given2XMLS_whenGeneratesOneDifference_thenCorrect(){
String myControlXML = "3 false ";
String myTestXML = "false 3 ";
Diff myDiff = DiffBuilder
.compare(myControlXML)
.withTest(myTestXML)
.withComparisonController(ComparisonControllers.StopWhenDifferent)
.build();
Iterator 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 at /struct[1]/int[1]
to 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 = "3 false ";
String testXml = "3 false ";
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("3 false "),
isSimilarTo(Input.from("3 false ")));
}
または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では、同一のXMLのみを確認しました。これは、類似のXMLでは、xmlunit-coreライブラリの機能を使用して少しカスタマイズする必要があるためです。
@Test
public void given2XMLS_whenSimilar_thenCorrect() {
String controlXml = "3 false ";
String testXml = "false 3 ";
assertThat(testXml, isSimilarTo(controlXml));
}
XMLには同様のノードがあるため、上記のテストに合格する必要がありますが、失敗します。 これは、XMLUnit compares control and test nodes at the same depth relative to the root nodeが原因です。
したがって、isSimilarTo条件は、isIdenticalTo条件よりもテストするのが少し興味深いです。 controlXmlのノード<int>3</int>は、testXmlの<boolean>false</boolean>と比較され、自動的に失敗メッセージが表示されます。
java.lang.AssertionError:
Expected: Expected element tag name 'int' but was 'boolean' -
comparing at /struct[1]/int[1] to at /struct[1]/boolean[1]:
3
but: result was:
false
ここで、XMLUnitのDefaultNodeMatcherクラスとElementSelectorクラスが役立ちます。
DefaultNodeMatcherクラスは、比較段階でXMLUnitによって参照され、controlXml,のノードをループして、testXmlのどのXMLノードをcontrolXmlで検出した現在のXMLノードと比較するかを決定します。 s。
その前に、DefaultNodeMatcherはすでにElementSelectorを参照して、ノードの照合方法を決定しています。
デフォルトの状態では、XMLUnitは深さ優先アプローチを使用してXMLをトラバースし、ドキュメントの順序に基づいてノードを照合するため、テストは失敗しました。したがって、<int>は<boolean>と照合されます。
合格するようにテストを微調整しましょう。
@Test
public void given2XMLS_whenSimilar_thenCorrect() {
String controlXml = "3 false ";
String testXml = "false 3 ";
assertThat(testXml,
isSimilarTo(controlXml).withNodeMatcher(
new DefaultNodeMatcher(ElementSelectors.byName)));
}
この場合、DefaultNodeMatcherに、XMLUnitがノードの比較を要求したときに、ノードを要素名で並べ替えて一致させる必要があることを伝えています。
最初に失敗した例は、ElementSelectors.DefaultをDefaultNodeMatcherに渡すのと似ていました。
または、xmlunit-matchersを使用するのではなく、xmlunit-coreからDiffを使用することもできます。
@Test
public void given2XMLs_whenSimilarWithDiff_thenCorrect() throws Exception {
String myControlXML = "3 false ";
String myTestXML = "false 3 ";
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ピースがidentical、similar、またはdifferentのいずれであるかを決定するのはクラスです。
次のXMLを検討してください。
and:
デフォルトの状態では、attr属性の値が異なるため、技術的には異なると評価されます。 テストを見てみましょう:
失敗メッセージ:
java.lang.AssertionError: Expected attribute value 'abc' but was 'xyz' -
comparing at /a[1]/b[1]/@attr
to 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 = "";
final String test = "";
Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withDifferenceEvaluator(new IgnoreAttributeDifferenceEvaluator("attr"))
.checkForSimilar().build();
assertFalse(myDiff.toString(), myDiff.hasDifferences());
}
今回は通ります。
7. 検証
XMLUnitは、Validatorクラスを使用してXML検証を実行します。 検証で使用するスキーマを渡しているときに、forLanguageファクトリメソッドを使用してインスタンスを作成します。
スキーマはその場所につながるURIとして渡され、XMLUnitはLanguagesクラスでサポートするスキーマの場所を定数として抽象化します。
通常、次のようにValidatorクラスのインスタンスを作成します。
Validator v = Validator.forLanguage(Languages.W3C_XML_SCHEMA_NS_URI);
この手順の後、XMLに対して検証する独自のXSDファイルがある場合は、そのソースを指定してから、XMLファイルソースを使用してValidatorのvalidateInstanceメソッドを呼び出します。
たとえば、students.xsdを見てください。
Andstudents.xml:
Rajiv
18
Candie
19
次に、テストを実行してみましょう。
@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 probs = r.getProblems().iterator();
while (probs.hasNext()) {
probs.next().toString();
}
assertTrue(r.isValid());
}
検証の結果は、ドキュメントが正常に検証されたかどうかを示すブールフラグを含むValidationResultのインスタンスです。
ValidationResultには、障害が発生した場合に備えて、IterableとValidationProblemsも含まれています。 students_with_error.xml.というエラーのある新しいXMLを作成しましょう。<student>ではなく、開始タグはすべて</studet>です。
Rajiv
18
Candie
19
次に、このテストを実行します:
@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 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 "".' }
ValidationProblem { line=6, column=4, type=ERROR, message='The element type "studet"
must be terminated by the matching end-tag "".' }
8. XPath
XPath式がXMLの一部に対して評価されると、一致するNodes.を含むNodeListが作成されます。
teachers.xmlというファイルに保存されている次のXMLについて考えてみます。
math
physics
political education
english
以下に示すように、XMLUnitは多くのXPath関連のアサーションメソッドを提供します。
teacherと呼ばれるすべてのノードを取得し、それらに対して個別にアサーションを実行できます。
@Test
public void givenXPath_whenAbleToRetrieveNodes_thenCorrect() {
Iterable i = new JAXPXPathEngine()
.selectNodes("//teacher", Input.fromFile(new File("teachers.xml")).build());
assertNotNull(i);
int count = 0;
for (Iterator 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ドキュメントを検証する方法を紹介しました。
これらすべての例とコードスニペットの完全な実装は、XMLUnitGitHub projectにあります。