Как стать автором
Обновить

Правильная проверка XML данных в java-проектах

Время на прочтение5 мин
Количество просмотров18K
Привет, %username%.

В ряде проектов мне потребовалось сравнивать XML данные в тестах.

Действительно, бывает, что результат работы твоего модуля — XML данные. Если это так, то как они генерятся нужно проверять в соответствии с принципами TDD. Я же в свою очередь стараюсь их придерживаться при разработке.

Под катом я постараюсь рассказать о том, как лучше всего, по моему мнению, тестировать генерацию XML в коде. В качестве инструмента сравнения XML я использовал XmlUnit.


Мне нужно быстро и удобно сравнивать XML данные в тестах. Изобретать велосипед не хотелось, и я выбрал наиболее популярную библиотеку для этих целей. В начале я постараюсь описать список проблем, которые я решал.

С чего всё началось


Как-то работая над очередным проектом, я сломал тест коллеги. Когда стал изучать тест, то нашел там что-то типа такого:
@Test
public void testSomeXmlGeneration() {
   // здесь подготавливаются данные для генератора
   String result = someModule.generateXML();
   // а теперь нешуточное сравнение XML данных
   File file = File.createTempFile("actial_data", ".xml");
   FileWriter fileWriter = new FileWriter(file);
   fileWriter.write(result);
   fileWriter.close();
   File fileExpect = new File(this.getClass().getResource("/expected_data.xml").getFile());
   FileAssert.assertBinaryEquals(fileExpect, file);
}


Здесь производится генерация данных и сравнение их с оригиналом из ресурсов. Для тестов используется JUnit4. Отмечу так же, что в идеале файл оригинала должен писаться вручную разработчиком.

Чем плох данный код?

Попробую обосновать по пунктам.

1. Слишком много телодвижений, чтобы сравнить два файла



Действительно, движений много, а если сравнивать приходится часто, то этот код в том или ином виде копируется туда-сюда, что собственно часто и происходит. Что-то с этим сделать мешает отсутствие времени.

2. Нет проверки валидности сгенеренного XML-кода



Из-за того, что валидность не проверялась, другой модуль отказывался обрабатывать невалидные данные, хотя по тестам было всё нормально. Хотелось решать эту проблему на стадии отладки теста.

3. Идентичные по структуре и данным XML могут оказаться неодинаковыми из-за разного порядка тегов или атрибутов



Например, так:
Вариант 1 Меняем порядок тегов
<?xml version="1.0" encoding="UTF-8"?>
<project>
    <name>some project</name>
    <description>desc</description>
</project>

<?xml version="1.0" encoding="UTF-8"?>
<project>
   <description>desc</description>
   <name>some project</name>
</project>


или так:
Вариант 2 Меняем порядок атрибутов
<?xml version="1.0" encoding="UTF-8"?>
<project name=”some project” description=”desc”/>

<?xml version="1.0" encoding="UTF-8"?>
<project description=”desc” name=”some project”/>



4. Хочу в тестах XML c форматированием



В тесте, представленном в начале статьи, XML данные были представленны без пробелов и переносов строк. Почувствуйте разницу:
Вариант 3 Удаляем пробелы и переносы строк
<?xml version="1.0" encoding="UTF-8"?>
<project>
     <name>some project</name>
     <description>desc</description>
</project>

<?xml version="1.0" encoding="UTF-8"?>
<project><description>desc</description><name>some project</name></project>



5. Автоматически обрабатывались переносы внутри данных



Вариант 3 Перенос строк внутри данных
<?xml version="1.0" encoding="UTF-8"?>
<project>
     <name>some project</name>
     <description>desc</description>
</project>

<?xml version="1.0" encoding="UTF-8"?>
<project>
     <name>
         some project
     </name>
     <description>desc</description>
</project>



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

Все выше описанные проблемы я решил устранить координатно:
  • воспользоваться какой-нибудь библиотекой для сравнения XML и проверки его валидности;
  • переписать все тесты так, чтобы они использовали эту библиотеку(благо их пока было ещё немного).


После недолгого поиска мой выбор пал на XmlUnit.

Основные способы подключения XmlUnit



Библиотека XmlUnit в первую очередь является расширением JUnit3.
Его основа — это класс XMLTestCase, наследник класса TestCase из JUnit3. Здесь вы можете посмотреть основные примеры использования класса.
На практике XmlUnit легко использовать и в других библиотеках тестов. Для этого есть класс Diff.

Итак, поехали


Для своих проектов я использую maven. Подключим XmlUnit как зависимость в maven. Для этого открываем pom.xml и в dependencies добавляем новую зависимость.
<dependencies>
 <!-- ….какие-то другие зависимости...  -->
    <dependency>
      <groupId>xmlunit</groupId>
      <artifactId>xmlunit</artifactId>
       <version>1.3</version>
    </dependency>
</dependencies>


Открываем тест, пишем туда новое сравнение

@Test
public void testSomeXmlGeneration() {
    // здесь подготавливаются данные для генератора
    String result = someModule.generateXML();
    Diff diff = new Diff(getResourceAsString("/expeced_data.xml"), result);
    assertTrue("XML результат не совпал", diff.similar());
}


Запускаем тест… и не работает. Ещё немного поискав в интернете, я нашел решение. Дело было в пробелах между тегами. Чтобы их не учитывать, нужно добавить предварительную настройку:

@Before
public void setUp() throws Exception {
    XMLUnit.setIgnoreComments(true);
    XMLUnit.setIgnoreWhitespace(true);
}


Вроде бы всё, можно радоваться результату. Однако, предположим, что мы допустили в XML ошибку. Тогда нам нужно знать, в каком именно теге проблема.
Решить её нам поможет следующий пример:

// файл с тестами
class TestXmls { 
  @Before
  public void setUp() throws Exception {
     XMLUnit.setIgnoreComments(true);
     XMLUnit.setIgnoreWhitespace(true);
  }

  @Test
  public void testSomeXmlGeneration() {
     // здесь подготавливаются данные для генератора
     String result = someModule.generateXML();
     Diff diff = new Diff(getResourceAsString("/expeced_data.xml"), result);
     Tools.showXmlDiff(diff);
     assertTrue("XML результат не совпал", diff.similar());
  }
}

// файл дополнительной библиотеки
public final class Tools {
  public static void showXmlDiff(Diff diff) {
     DetailedDiff detDiff = new DetailedDiff(diff);
     List differences = detDiff.getAllDifferences();
     for (Object object : differences) {

            Difference difference = (Difference) object;
            System.out.println("***********************");
            System.out.println(difference);
            System.out.println("***********************");
     }
   }
}


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

Что ещё можно сделать хорошего?





Литература



Ссылки, по которым я черпал информацию.



UPD: По результатам дискуссии с Lure_of_Chaos добавил ещё ссылку в список литературы. Там можно прочитать про валидацию XML и про другие красивые фишки, о которых почему-то не написано в официальной документации.

Ещё раз отмечу, что XmlUnit позволяет проверять валидность XML по DTD и XSD схемам.
Как правильно отметил Lure_of_Chaos, в схемах XSD можно требовать порядка задания элементов. Проверять это в тестах — критически важно.

UPD2: поправил последний пример проверки. Спасибо, Colwin.
На этом всё, спасибо за внимание.
Теги:
Хабы:
Всего голосов 37: ↑33 и ↓4+29
Комментарии17

Публикации