Привет, %username%.
В ряде проектов мне потребовалось сравнивать XML данные в тестах.
Действительно, бывает, что результат работы твоего модуля — XML данные. Если это так, то как они генерятся нужно проверять в соответствии с принципами TDD. Я же в свою очередь стараюсь их придерживаться при разработке.
Под катом я постараюсь рассказать о том, как лучше всего, по моему мнению, тестировать генерацию XML в коде. В качестве инструмента сравнения XML я использовал XmlUnit.
Мне нужно быстро и удобно сравнивать XML данные в тестах. Изобретать велосипед не хотелось, и я выбрал наиболее популярную библиотеку для этих целей. В начале я постараюсь описать список проблем, которые я решал.
Как-то работая над очередным проектом, я сломал тест коллеги. Когда стал изучать тест, то нашел там что-то типа такого:
Здесь производится генерация данных и сравнение их с оригиналом из ресурсов. Для тестов используется JUnit4. Отмечу так же, что в идеале файл оригинала должен писаться вручную разработчиком.
Чем плох данный код?
Попробую обосновать по пунктам.
Действительно, движений много, а если сравнивать приходится часто, то этот код в том или ином виде копируется туда-сюда, что собственно часто и происходит. Что-то с этим сделать мешает отсутствие времени.
Из-за того, что валидность не проверялась, другой модуль отказывался обрабатывать невалидные данные, хотя по тестам было всё нормально. Хотелось решать эту проблему на стадии отладки теста.
Например, так:
или так:
В тесте, представленном в начале статьи, XML данные были представленны без пробелов и переносов строк. Почувствуйте разницу:
В рассматриваемом в начале статьи тесте, исходные данные были в одну строчку, только тегов и атрибутов было несколько больше. Посадить туда ошибку — очень просто, а исправлять так, чтобы тест проходил, — долго и мучительно.
Все выше описанные проблемы я решил устранить координатно:
После недолгого поиска мой выбор пал на XmlUnit.
Библиотека XmlUnit в первую очередь является расширением JUnit3.
Его основа — это класс XMLTestCase, наследник класса TestCase из JUnit3. Здесь вы можете посмотреть основные примеры использования класса.
На практике XmlUnit легко использовать и в других библиотеках тестов. Для этого есть класс Diff.
Для своих проектов я использую maven. Подключим XmlUnit как зависимость в maven. Для этого открываем pom.xml и в dependencies добавляем новую зависимость.
Открываем тест, пишем туда новое сравнение
Запускаем тест… и не работает. Ещё немного поискав в интернете, я нашел решение. Дело было в пробелах между тегами. Чтобы их не учитывать, нужно добавить предварительную настройку:
Вроде бы всё, можно радоваться результату. Однако, предположим, что мы допустили в XML ошибку. Тогда нам нужно знать, в каком именно теге проблема.
Решить её нам поможет следующий пример:
Обратите внимание на метод showXmlDiff. В нем получаем список различий и выводим его.
Ссылки, по которым я черпал информацию.
UPD: По результатам дискуссии с Lure_of_Chaos добавил ещё ссылку в список литературы. Там можно прочитать про валидацию XML и про другие красивые фишки, о которых почему-то не написано в официальной документации.
Ещё раз отмечу, что XmlUnit позволяет проверять валидность XML по DTD и XSD схемам.
Как правильно отметил Lure_of_Chaos, в схемах XSD можно требовать порядка задания элементов. Проверять это в тестах — критически важно.
UPD2: поправил последний пример проверки. Спасибо, Colwin.
На этом всё, спасибо за внимание.
В ряде проектов мне потребовалось сравнивать 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 | Меняем порядок тегов |
---|---|
|
|
или так:
Вариант 2 | Меняем порядок атрибутов |
---|---|
|
|
4. Хочу в тестах XML c форматированием
В тесте, представленном в начале статьи, XML данные были представленны без пробелов и переносов строк. Почувствуйте разницу:
Вариант 3 | Удаляем пробелы и переносы строк |
---|---|
|
|
5. Автоматически обрабатывались переносы внутри данных
Вариант 3 | Перенос строк внутри данных |
---|---|
|
|
В рассматриваемом в начале статьи тесте, исходные данные были в одну строчку, только тегов и атрибутов было несколько больше. Посадить туда ошибку — очень просто, а исправлять так, чтобы тест проходил, — долго и мучительно.
Все выше описанные проблемы я решил устранить координатно:
- воспользоваться какой-нибудь библиотекой для сравнения 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. В нем получаем список различий и выводим его.
Что ещё можно сделать хорошего?
- XmlUnit помимо сравнения предлагает функции проверки XML-данных на валидность.
- Можно применять XmlUnit для тестирования XSLT-преобразований.
- И так далее и тому подобное...
Литература
Ссылки, по которым я черпал информацию.
- Официальная документация по XmlUnit
- Примеры использования
- Мануал XmlUnit
- Вопрос на stackoverflow с кучей полезных ответов
- Список популярных утилит и библиотек по сравнению
- UPD:Ещё примеры использования XmlUnit
UPD: По результатам дискуссии с Lure_of_Chaos добавил ещё ссылку в список литературы. Там можно прочитать про валидацию XML и про другие красивые фишки, о которых почему-то не написано в официальной документации.
Ещё раз отмечу, что XmlUnit позволяет проверять валидность XML по DTD и XSD схемам.
Как правильно отметил Lure_of_Chaos, в схемах XSD можно требовать порядка задания элементов. Проверять это в тестах — критически важно.
UPD2: поправил последний пример проверки. Спасибо, Colwin.
На этом всё, спасибо за внимание.