Содержание


В погоне за качеством кода

Откройте XMLUnit

Расширенная среда JUnit для тестирования XML-документов

Comments

Серия контента:

Этот контент является частью # из серии # статей: В погоне за качеством кода

Следите за выходом новых статей этой серии.

Этот контент является частью серии:В погоне за качеством кода

Следите за выходом новых статей этой серии.

Иногда в цикле разработки программного обеспечения необходимо проверять структуру или содержимое 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

Если появляется ощущение, что работы стало слишком много, можно предположить, что кто-то другой нашел более простой способ решения проблемы. Когда речь идет о программной проверке XML-документов, первое, что приходит на ум, это XMLUnit.

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

Самый простой способ заключается в использовании XMLUnit для логического сравнения XML-документов со стандартными контрольными файлами. По существу, это тестирование на различия: Имея правильный XML-документ, можно сравнить, создает ли приложение такой же документ? Это относительно простой тест, но его можно использовать для проверки структуры и содержимого XML-документа. С помощью XPath можно также проверить определенное содержимое.

Проверка содержимого

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());		
}

Разумеется, 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 также будет полезно!

Зачем работать больше?

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

Хотя в настоящее время нет новых версий XMLUnit, текущий набор его функций достаточно надежен для обеспечения множества функций тестирования, и при этом совершенно бесплатно!


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи In pursuit of code quality: Discover XMLUnit;
  • "Категоризация тестирования для быстрой сборки" (Эндрю Гловер, developerWorks, октябрь 2006 г.): Эндрю Гловер выделяет три категории тестов, необходимых для обеспечения надежности всей системы и показывает, как автоматически сортировать и выполнять тесты по категориям;
  • "Тестирование производительности с помощью JUnitPerf" (Эндрю Гловер, developerWorks, ноябрь 2006 г.): Эндрю Гловер создает тестирование производительности в составе цикла разработки и представляет два простых способа выполнения тестирования;
  • "Java XPath API" (Эллиотт Расти Гарольд, developerWorks, июль 2006 г.): Эллиотт Гарольд демонстрирует новый XPath API среды Java 5;
  • "Проверка XML - простой способ" (Эндрю Гловер, testearly.com, март 2006 г.): Краткое введение в XMLUnit;
  • Использование выражений JUnit в TestNG (thediscoblog.com): Как использования выражения JUnit в TestNG;
  • Зона технологии Java: Сотни статей по каждому аспекту программирования на Java;
  • Загрузите JUnit: Узнайте новости о JUnit 4;
  • Загрузите TestNG: Эффективная, удобная в работе среда тестирования на основе JUnit и NUnit;
  • Загрузите XMLUnit: XMLUnit представляет собой расширенную среду JUnit, упрощающую для разработчиков процесс тестирования XML-документов.

Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=208644
ArticleTitle=В погоне за качеством кода: Откройте XMLUnit
publish-date=04112007