В гонитві за якістю коду: Відкрийте XMLUnit (исходники), HTML, XML, DHTML, Інтернет-технології, статті

Іноді в циклі розробки програмного забезпечення необхідно перевіряти структуру або вміст XML-документів. Незалежно від типу розробляється тестування XML-документів створює деякі складності, особливо при відсутності інструментів, що полегшують процес перевірки.


У цьому місяці спочатку буде показано, чому для перевірки структури та вмісту XML-документів не слід використовувати порівняння String. Потім буде представлена ​​середу XMLUnit – інструмент XML-перевірки, створений спеціально для розробників Java, і буде показано, як його використовувати для перевірки XML-документів.


Старе добре порівняння рядків


Для початку уявимо, що створено додаток, що виводить XML-документ, що представляє об’єктно-залежний звіт. Звіт створюється для даного набору класів і відповідних фільтрів і виводить клас і його залежності (слід подумати про імпорт).


У лістингу 1 показаний звіт для даного списку класів com.acme.web.Widget і com.acme.web.Account з фільтрами, ігнорують зовнішні класи, наприклад, java.lang.String:


Лістинг 1. Приклад залежності XML-звіту




<DependencyReport date=”Sun Dec 03 22:30:21 EST 2006″>
<FiltersApplied>
<Filter pattern=”java/org”/>
<Filter pattern=”net.”/>
</FiltersApplied>
<Class name=”com.acme.web.Widget”>
<Dependency name=”com.acme.resource.Configuration”/>
<Dependency name=”com.acme.xml.Document”/>
</Class>
<Class name=”com.acme.web.Account”>
<Dependency name=”com.acme.resource.Configuration”/>
<Dependency name=”com.acme.xml.Document”/>
</Class>
</DependencyReport>

Лістинг 1 створюється додатком, отже, перший рівень тестування полягає у перевірці здатності програми сформувати документ. Після цього слід протестувати хоча б три аспекти конкретного документа:



Перші два аспекти можна обробити за допомогою JUnit і використання порівнянь String (Див. лістинг 2):


Лістинг 2. Перевірка XML за допомогою жорсткого кодування




public class XMLReportTest extends TestCase {
private Filter[] getFilters(){
Filter[] fltrs = new Filter[2];
fltrs[0] = new RegexPackageFilter(“java/org”);
fltrs[1] = new SimplePackageFilter(“net.”);
return fltrs;
}
private Dependency[] getDependencies(){
Dependency[] deps = new Dependency[2];
deps[0] = new Dependency(“com.acme.resource.Configuration”);
deps[1] = new Dependency(“com.acme.xml.Document”);
return deps;
}
public void testToXML() {
Date now = new Date();
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(now, this.getFilters());
report.addTargetAndDependencies(
“com.acme.web.Widget”, this.getDependencies());
report.addTargetAndDependencies(
“com.acme.web.Account”, this.getDependencies());
String valid = “<DependencyReport date=”” + now.toString() + “”>”+
“<FiltersApplied><Filter pattern=”java/org” /><Filter pattern=”net.” />”+
“</FiltersApplied><Class name=”com.acme.web.Widget”>” +
” <Dependency name=”com.acme.resource.Configuration” />”+
“<Dependency name=”com.acme.xml.Document” /></Class>”+
“<Class name=”com.acme.web.Account”>”+
“<Dependency name=”com.acme.resource.Configuration” />”+
“<Dependency name=”com.acme.xml.Document” />”+
“</Class></DependencyReport>”;
assertEquals(“report didn”t match xml”, valid, report.toXML());
}
}

Тестування, представлене в лістингу 2, має кілька основних недоліків, серед яких не тільки жорстко закодовані порівняння String. По-перше, тестування не є чітко написаним. По-друге, воно вкрай нестійке. При зміні формату XML-документа (включаючи додавання пробілу), простіше вкласти нову копію документа, ніж намагатися виправити код String. Нарешті, сутність тестування змушує розробників боротися з аспектом Date, Навіть якщо цей аспект ні на що не впливає.


А якщо потрібно переконатися, що значення name другого елементу Class в документі – com.acme.web.Account? Зрозуміло, можна використовувати регулярні вирази або пошук String, Але це процес досить трудомісткий. Чи не краще управляти DOM безпосередньо за допомогою аналізує середовища?







 



XMLUnit з TestNG?

XMLUnit являє собою розширення JUnit, але це не означає, що його не можна використовувати в TestNG. В TestNG можна вмонтувати практично всю середу за умови, що в ній є API, що підтримує передачу повноважень, і не заснований на декоратора.


Тестування за допомогою XMLUnit


Якщо з’являється відчуття, що роботи стало занадто багато, можна припустити, що хтось інший знайшов більш простий спосіб вирішення проблеми. Коли мова йде про програмну перевірці XML-документів, перше, що приходить на розум, це XMLUnit.


XMLUnit являє собою розширену середу JUnit, що спрощує для розробників процес тестування XML-документів. Фактично XMLUnit можна назвати справжнім хет-триком для XML-тестування: цю середу можна використовувати для перевірки структури XML-документа, його вмісту і навіть для перевірки певних фрагментів документів.


Найпростіший спосіб полягає у використанні XMLUnit для логічного порівняння XML-документів зі стандартними контрольними файлами. По суті, це тестування на відмінності: Маючи правильний XML-документ, можна порівняти, чи створює додаток такий же документ? Це відносно простий тест, але його можна використовувати для перевірки структури та вмісту XML-документа. За допомогою XPath можна також перевірити певний вміст.







 



Делегування, а не наслідування!

В якості основного правила уникайте спадкування тестування, де тільки можливо. Багато розширені середовища JUnit, включаючи XMLUnit, пропонують спеціалізовані тести, які можуть успадковуватися для спрощення тестування певної архітектури. Тестування, успадковують класи із середовища, страждають від відсутності гнучкості через ієрархічної парадигми платформи Java прямого спадкуванням. Більш часто такі розширені середовища JUnit пропонують делегування API, що спрощує комбінування різних середовищ без проблем з жорсткою структурою успадкування.


Перевірка вмісту


XMLUnit можна використовувати за допомогою делегування або успадкування. В якості основного правила я рекомендую уникати успадкування при тестуванні. З іншого боку, спадкування з XMLTestCase середовища XMLUnit надає деякі зручні методи контролю (відмінні від static і, отже, на них не можна посилатися статично, як в TestCase JUnit).


Незалежно від вибору режиму використання XMLUnit, необхідно ініціалізувати аналізатори XMLUnit. Їх можна ініцілізіровать за допомогою викликів System.setProperty або за допомогою методів static базового класу XMLUnit.


Після ініціалізації XMLUnit з різними необхідними аналізаторами можна використовувати клас Diff, Що представляє собою центральний механізм логічного порівняння двох XML-документів. У лістингу 3 тест testToXML поліпшений за допомогою XMLUnit:


Лістинг 3. Покращений тест testToXML




public class XMLReportTest extends TestCase {
protected void setUp() throws Exception {
XMLUnit.setControlParser(
“org.apache.xerces.jaxp.DocumentBuilderFactoryImpl”);
XMLUnit.setTestParser(
“org.apache.xerces.jaxp.DocumentBuilderFactoryImpl”);
XMLUnit.setSAXParserFactory(
“org.apache.xerces.jaxp.SAXParserFactoryImpl”);
XMLUnit.setIgnoreWhitespace(true);
}
private Filter[] getFilters(){
Filter[] fltrs = new Filter[2];
fltrs[0] = new RegexPackageFilter(“java/org”);
fltrs[1] = new SimplePackageFilter(“net.”);
return fltrs;
}
private Dependency[] getDependencies(){
Dependency[] deps = new Dependency[2];
deps[0] = new Dependency(“com.acme.resource.Configuration”);
deps[1] = new Dependency(“com.acme.xml.Document”);
return deps;
}
public void testToXML() {
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(1165203021718L),
this.getFilters());
report.addTargetAndDependencies(
“com.acme.web.Widget”, this.getDependencies());
report.addTargetAndDependencies(
“com.acme.web.Account”, this.getDependencies());
Diff diff = new Diff(new FileReader(
new File(“./test/conf/report-control.xml”)),
new StringReader(report.toXML()));
assertTrue(“XML was not identical”, diff.identical());
}
}

Зверніть увагу, як ініціалізувалися методи setControlParser, setTestParser і setSAXParserFactory середовища XMLUnit. Для цих значень можна використовувати будь-яку аналізує середу, сумісну з JAXP. Також зверніть увагу, що метод setIgnoreWhitespace викликається зі значенням true – Повірте мені, це рятівний засіб! В іншому випадку доведеться зіткнутися з безліччю помилок у випадку, якщо два документи різняться невідповідністю прогалин!


Порівняння з допомогою Diff


Клас Diff підтримує два типи порівнянь: identical і similar. Якщо два порівнюваних документа мають однакову структуру і значення (прогалини ігноруються, якщо встановлений соответстующих прапор), то вони вважаються ідентичними, якщо два документи ідентичні, вони також є подібними. Зворотне твердження не обов’язково буде вірним.


Наприклад, в лістингу 4 показаний простий фрагмент XML, логічно подібний XML, представленому в лістингу 5. Але ці фрагменти не є ідентичними:


Лістинг 4. Фрагмент XML облікового запису




<account>
<id>3A-00</id>
<name>acme</name>
</account>

Фрагмент XML в лістингу 5 представляє той же самий логічний документ, що і в лістингу 4. XMLUnit не вважає ці фрагмент ідентичними, оскільки елементи name і id помінялися місцями.


Лістинг 5. Подібний фрагмент XML




<account>
<name>acme</name>
<id>3A-00</id>
</account>

Відповідно, можна написати тест для перевірки поведінки XMLUnit (див. лістинг 6):


Лістинг 6. Тест для перевірки подібності та ідентичності




public void testIdenticalAndSimilar() throws Exception {
String controlXML = “<account><id>3A-00</id><name>acme</name></account>”;
String testXML = “<account><name>acme</name><id>3A-00</id></account>”;
Diff diff = new Diff(controlXML, testXML);
assertTrue(diff.similar());
assertFalse(diff.identical());
}

Різниця між подібними і ідентичними XML-документами досить тонке, але можливість перевірки обох типів документів може виявитися вкрай корисною, наприклад, при тестуванні документів, що створюються різними додатками або клієнтами.


Перевірка структури


Крім перевірки вмісту іноді необхідно перевірити структуру XML-документа. В цьому випадку значення окремих елементів і атрибутів ролі не грають, необхідно піклуватися тільки про структуру.


На щастя, можна знову використовувати тестування, визначене в лістингу 3, для перевірки структури документа. При цьому текстові значення елементів і значення атрибутів ігноруються. Це робиться за допомогою виклику методу overrideDifferenceListener() в класі Diff та надання йому класу IgnoreTextAndAttributeValuesDifferenceListener, Що отримується з XMLUnit. Відредагований тест представлений у лістингу 7:


Лістинг 7. Перевірка XML-структури без значень атрибутів




public void testToXMLFormatOnly() throws Exception{
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(), this.getFilters());
report.addTargetAndDependencies(
“com.acme.web.Widget”, this.getDependencies());
report.addTargetAndDependencies(
“com.acme.web.Account”, this.getDependencies());

Diff diff = new Diff(new FileReader(
new File(“./test/conf/report-control.xml”)),
new StringReader(report.toXML()));
diff.overrideDifferenceListener(
new IgnoreTextAndAttributeValuesDifferenceListener());
assertTrue(“XML was not similar”, diff.similar());
}






 



Подібні не означає ідентичні!

При використанні класу IgnoreTextAndAttributeValuesDifferenceListener необхідно оголосити, що два документи є similar і не identical. Якщо помилково викликати identical, Будуть оброблятися значення атрибутів.


Зрозуміло, DTD та XML-схеми спрощують перевірку XML-структури, але іноді в документах немає посилань на ці схеми. В таких сценаріях можна виконати тільки перевірку структури. До того ж, якщо потрібно пропустити певні значення (наприклад, Date) Можна реалізувати інтерфейс DifferenceListener (Як зроблено в класі IgnoreTextAndAttributeValuesDifferenceListener) І надати можливість для користувача реалізації.


XMLUnit з XPath


Для завершення хет-трику XML-тестування XMLUnit за допомогою XPath спрощує перевірку певних фрагментів XML-документа.


Наприклад, використовуючи формат документа, представленого в лістингу 1, хотілося б переконатися, що значення атрибута name першого елемента Class, Створеного додатком, відпо com.acme.web.Widget. Для цього необхідно створити вираз XPath для переходу до точного положенню; потім XMLUnit “s XMLTestCase надає метод assertXpathExists(), Що означає необхідність розширення XMLTestCase.


Лістинг 8. Використання XPath для перевірки точних XML-значень




public void testToXMLFormatOnly() throws Exception{
BatchDependencyXMLReport report =
new BatchDependencyXMLReport(new Date(), this.getFilters());
report.addTargetAndDependencies(
“com.acme.web.Widget”, this.getDependencies());
report.addTargetAndDependencies(
“com.acme.web.Account”, this.getDependencies());

assertXpathExists(“//Class[1][@name=”com.acme.web.Widget”]”,
report.toXML());
}


Як видно в лістингу 8, XMLUnit разом з XPath надає зручний механізм для перевірки точних аспектів XML-документа замість виконання великого тестування відмінностей. Слід враховувати, що для використання переваг XPath в XMLUnit тестування має розширювати XMLTestCase. Знайомство з XPath також буде корисно!







 



X-що?
XPath або XML Path Language являє собою мова виразів з адресацією до фрагментів XML-документа на основі деревовидного представлення. XPath дозволяє переглядати XML-документ і спрощує вибір значень документа.
 

Навіщо працювати більше?


XMLUnit являє собою інструмент з відкритим вихідним кодом на основі Java, що дозволяє простіше тестувати XML-документи і забезпечує більшу гнучкість в порівнянні з String. Єдиний можливий недолік використання XMLUnit для тестування відмінностей полягає в тому, що тести покладаються на файлову систему для завантаження перевіряється документа. Це обумовлює додаткову залежність при створенні тестів.


Хоча в даний час немає нових версій XMLUnit, поточний набір його функцій досить надійний для забезпечення безлічі функцій тестування, і при цьому абсолютно безкоштовно!

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*