Тестирование maven плагинов
Часть моей хобби инфраструктуры завязана на .deb пакеты и apt репозитории. Чтобы со всем этим работать из java, я написал несколько maven плагинов и выложил в открытый доступ. Один из этих плагинов - deb-maven-plugin. Он позволяет собирать из java проекта .deb пакет. Этот формат пакетов имеет достаточно сложную структуру и большое историческое наследие. Я очень тщательно разобрался с тем, как собирать такой пакет для java проектов и создал плагин, который сильно облегчает сборку. Со временем ко мне приходили различные разработчики и добавляли в этот плагин нужную им функциональность. Мы вместе исправляли ошибки. Всю идиллию портило только одно - полное отсутствие тестов. Каждое изменение, которое мы добавляли, могло потенциально сломать уже существующие проекты.
Однажды я решил проверить собранный пакет с помощью утилиты lintian и обнаружил множество критичных ошибок. Это сподвигло меня собраться с мыслями и всё-таки разобраться, как тестируются maven плагины. И поправить ошибки, о которых предупреждал lintian.
Требования
Прежде чем начать писать тесты, необходимо определиться какие: интеграционные или юнит. Юнит-тесты для maven плагинов достаточно бесполезны. Дело в том, что maven плагин - это один большой класс, реализующий AbstractMojo. Иногда полезно иметь несколько вспомогательных классов, чтобы декомпозировать задачу. Но в большинстве случаев - это один большой класс, который нужно тестировать интеграционно.
Ещё одну вещь, которую юнит тесты не смогут проверить - то, как конфигурация пробрасывается внутрь плагина. До версии 3.0 каноничным способом указания конфигурации был javadoc определённого формата. Это крайне плохой и неудобный способ задания внешних параметров.
/**
* @parameter default-value="${project.basedir}/src/main/deb";
*/
private String debBaseDir;
Соответственно необходимо протестировать правильность написания этого javadoc. Без полноценного запуска PlexusContainer это достаточно сложно. PlexusContainer - это DI контейнер, который чем-то похож на Spring и Dagger. Однако, из-за своей малой распространённости он имеет крайне скудную документацию. Определить какие компоненты доступны в контейнере можно только методом проб и ошибок. Основные настройки плагина можно получать из MavenProject:
/**
* The maven project.
*
* @parameter property="project"
* @readonly
*/
private MavenProject project;
Такую конфигурацию можно проверить только интеграционными тестами. Нужно честно запустить PlexusContainer и загрузить тестовый pom.xml.
Если начать искать в сети “тестирование maven плагинов”, то можно найти официальную документацию. В ней как раз описаны два способа:
- Интеграционные тесты с помощью плагина maven-plugin-testing-harness
- Функциональные тесты с помощью maven-verifier
Мне идеально подходил первый способ. Но для полноты картины я решил разобраться в чём же заключается второй способ. По своей сути maven-verifier ищет инсталляцию maven в системном свойстве MVN_HOME и запускает в отдельном процессе mvn <some plugin>
. После этого проверяется, что процесс завершился без ошибки, что нет логов с ошибками и тому подобное. Этот способ идеально подходит для тестирования плагинов с разными версиями maven. В моём же случае хотелось бы запускать maven в одной и той же JVM, что и юнит тесты. Это быстро и позволяет оценить покрытие кода тестами.
Интеграционные тесты
Свои тесты я предполагал сделать следующим образом:
- создать конфигурацию проекта success, в которой использованы всевозможные настройки плагина
- написать тест для конфигурации success
- для того чтобы протестировать опции по-умолчанию или граничные значения, необходимо взять конфигурацию success и изменить (перезаписать) параметры
В результате у меня получился следующий тест:
@Rule
public MojoRule mrule = new MojoRule();
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testSuccess() throws Exception {
File basedir = new File("src/test/resources/success");
MavenProject mavenProject = mrule.readMavenProject(basedir); // загрузка конфигурации проекта из pom.xml
mavenProject.getBuild().setDirectory(folder.getRoot().getAbsolutePath());
Mojo mm = mrule.lookupConfiguredMojo(mavenProject, "package"); // загрузка и конфигурирования Mojo для тестирования
mm.execute();
assertEquals(1, mavenProject.getAttachedArtifacts().size()); // получение .deb пакета
Artifact artifact = mavenProject.getAttachedArtifacts().get(0);
assertDeb(new File("src/test/resources/expected/success"), artifact.getFile(), artifact.getClassifier()); // сравнение директории с ожидаемыми файлами и содержимого .deb пакета
}
- MojoRule - это вспомогательный класс для удобной работы с PlexusContainer. В нём есть множество удобных методов для загрузки плагина из pom.xml и инициализации вспомогательных классов.
- TemporaryFolder - это правило для junit, которое создаёт временную папку вначале теста и удаляет её в конце.
В таком тесте можно загружать гарантированно правильную конфигурацию проекта, менять настройки MavenProject и проверять всевозможные граничные значения.
Самым сложным и одновременно долгим и неприятным оказалась реализация метода assertDeb
. По сути этот метод должен рекурсивно сравнивать директории в ожидаемой папке и внутренности .deb пакета.
После того как я написал этот тест, обнаружилось множество ошибок в тестируемом коде. В частности структура .tar архива, который входит в .deb пакет, создавалась неправильно. По стандарту все вложенные директории должны присутствовать в .tar файле. Например, для файла usr/share/doc/copyright должны быть следующие записи в .tar:
usr/
usr/share/
usr/share/doc/
usr/share/doc/copyright
Я же ошибочно создавал только:
usr/share/doc/copyright
Выводы
Писать тесты для maven плагинов можно. Однако, это требует достаточно высокий порог вхождения и глубокого понимания того, как работает maven. Нужно быть как минимум готовым к тому, что придётся заглянуть в исходники. Результат же стоит того. Автоматические тесты позволяют спокойно редактировать код плагина без опаски сломать обратную совместимость с сотнями других проектов, которые его используют. Причём редактирование включает в себя улучшение производительности. А представьте, что 15% улучшение сможет улучшить тысячи сборок сотен проектов на 15%!