Проблема
Основным средством для командной работы с репозиторием компании, в которой я работаю, является Gitlab. На первый взгляд, он хорош: и бесплатной версии хватает сполна, и CI/CD - пайплайн имеется, и хранить артефакты тоже можно (долой Nexus). Однако, и у GitLab есть свои минусы.
Так, например, разработчики до сих пор не смогли реализовать визуализацию покрытия тестов. Да, она у них есть, но для её реализации нужно сгенерировать и загрузить в GitLab отчет о покрытии в устаревшем формате Cobertura, а не в актуальном Jacoco. Основная же проблема в том, что Cobertura plugin последний раз обновлялся в далеком 2015 году.
То есть этот плагин уже не поддерживается Java 8. Прикрепляю ссылки на официальные источники, чтобы вы могли понять, насколько там все запущено))
И что же нам предлагает сделать GitLab? Он сделал все «по-человечески»? Нет))) он предложил использовать скрипт на Python для перевода отчета в формате Jacoco в отчет в формате Cobertura.
Выглядит это так:
test-jdk11:
stage: test
image: maven:3.6.3-jdk-11
script:
- mvn $MAVEN_CLI_OPTS clean org.jacoco:jacoco-maven-plugin:prepare-agent test jacoco:report
artifacts:
paths:
- target/site/jacoco/jacoco.xml
coverage-jdk11:
# Must be in a stage later than test-jdk11's stage.
# The `visualize` stage does not exist by default.
# Please define it first, or choose an existing stage like `deploy`.
stage: visualize
image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.7
script:
# convert report from jacoco to cobertura, using relative project path
- python /opt/cover2cover.py target/site/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > target/site/cobertura.xml
needs: ["test-jdk11"]
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: target/site/cobertura.xml
Подробнее об этом решении по ссылке.
Громоздко, правда? Даже приходится подключать дополнительный образ, чтобы скрипт заработал.
Наша компания не единственная, кто заметил эту проблему. Так в 2020 году Markus Schuch создал запрос разработчикам GitLab о добавлении поддержки отчетов о покрытии в формате Jacoco. Но решение данной проблемы так и не сдвинулось с места. Поэтому руководством нашей компании мне было поручено написать плагин, который будет переводить отчет jacoco.xml в cobertura.xml. Таким образом, в компании решили сразу 2 задачи: и получили необходимый функционал, и заняли делом джуна.
Решение
Было решено написать плагин, который упростит создание отчета в формате Cobertura. Принцип работы плагина: перевести отчет формата Jacoco в Cobertura.
Проблемы написания плагина
Подбор библиотек
Сначала для парсинга файла формата XML необходимо было подобрать библиотеку. Так как существует множество разновидностей библиотек со своими плюсами и минусами выбор оказался непростым.
Например, DOM парсер – это древовидный синтаксический анализатор. Таким парсером очень удобно искать элементы в файле, но у него есть недостаток – скорость.
SAX парсер работает иначе, он не создает никакой доменной структуры, а последовательно читает/записывает XML файл. Поэтому SAX парсер работает гораздо быстрее чем DOM, но он не умеет так же хорошо искать элементы файла. Поэтому мной была выбрана библиотека JDOM2 для чтения jacoco.xml и SAX для записи в cobertura.xml.
Написание алгоритма преобразования Jacoco в Cobertura
Основная идея алгоритма была позаимствована из питоновского скрипта cover2cover.py. Однако, немного изменить логику алгоритма всё же пришлось. Например, различная работа XML библиотек в Python и Java, различный результат при математических вычислениях чисел с плавающей запятой.
После того как стало понятно, что алгоритм, написанный на Java, выдает такие же отчеты, что и cover2cover.py необходимо было реализовать удобство применения в сторонних java приложениях. Очевидно, что для этого необходимо написать плагин. О том как написать плагин есть хорошая статья.
Всего в мой плагин можно передать 3 переменные:
source - путь к файлу формата Jacoco (дефолтное значение:
target/site/jacoco/jacoco.xml
);result - путь, куда файл cobertura.xml будет размещен после работы плагина (дефолтное значение:
target/site/cobertura/cobertura.xml
);pathsToProject - список путей к классам относительно корня проекта (дефолтное значение:
/src/main/java/
).
Важно, чтобы по пути, указанному в source, лежал jacoco.xml, иначе перевод не отработает. Также в pathsToProject можно передавать как один путь к пакетам, так и несколько путей, если проект мультимодульный.
Например, для проекта со структурой:
pet-parent/
+- pet/
+- src/
+- main/
+- java/
+- pom.xml
...
+- pet-api/
+- src/
+- main/
+- java/
+- pom.xml
...
+- pet-test/
+- pom.xml
+- pom.xml
Необходимо передать пути /pet-api/src/main/java/
и /pet/src/main/java/
. Теперь покрытия тестами обоих модулей будут видны!
Как пользоваться
В pom.xml
Плагин очень просто добавить в pom.xml, и он будет генерировать отчет cobertura.xml туда, куда Вам потребуется. В общем, работает он также, как и Jacoco плагин, а главное условие выполнения - правильно указать параметры и поставить его в pom.xml после Jacoco плагина.
Вот фрагмент pom.xml, в котором наглядно показано, как настраивать плагин:
<project>
...
<build>
<!-- To use the plugin goals in your POM or parent POM -->
<plugins>
...
<plugin>
<groupId>ru.siblion.lab</groupId>
<artifactId>jacocoToCobertura-maven-plugin</artifactId>
<version>0.0.2</version>
<executions>
<execution>
<goals>
<goal>convert</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<source>path to jacoco.xml</source>
<result>path to cobertura.xml</result>
<pathsToProject>path to project files</pathsToProject>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
А для мультимодульного проекта (возьмем пример из прошлой главы) параметр pathsToProject будет выглядеть так:
<pathsToProject>/pet/src/main/java/,/pet-api/src/main/java/</pathsToProject>
То есть просто перечисляем через запятую. Теперь каждый раз после запуска тестов будет появляться отчет в формате Cobertura)))
В .gitlab-ci.yml необходимо будет добавить, чтобы GitLab увидел отчет:
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: target/cobertura/cobertura.xml
В CI/CD пайплайне
Иногда не хочется лишний раз изменять pom.xml, чтобы ничего не поломалось. Поэтому работу jacocoToCobertura плагина можно настроить в файле .gitlab-ci.yml:
mvn-test:
stage: test
script:
- "./mvnw -ntp -s .m2/settings.xml --batch-mode clean test"
artifacts:
paths:
- target/site/jacoco/jacoco.xml
only:
- merge_requests
- main
coverage:
stage: visualize
script:
- "./mvnw $MAVEN_CLI_OPTS dependency:get -Dartifact=ru.siblion.lab:jacocoToCobertura-maven-plugin:0.0.2"
- "./mvnw $MAVEN_CLI_OPTS ru.siblion.lab:jacocoToCobertura-maven-plugin:0.0.2:convert
-Dsource="target/site/jacoco/jacoco.xml"
-Dresult="target/site/cobertura/cobertura.xml"
-DpathesToProject="${CI_PROJECT_DIR}/src/main/java/""
coverage: "/Total.*?([0-9]{1,3})%/"
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: target/site/cobertura/cobertura.xml
only:
- merge_requests
- main
Результат
Теперь после успешного выполнения пайплайна в изменениях в merge request появится красивая подсветка, которая показывает какой код покрыт тестами, а какой нет.
Кроме того, после выполнения пайплайна итоговое покрытие проекта будет отображаться в Jobs пайплайна.
Технические ограничения
Плагин написан на Java 17. Требуется наличие Jacoco plugin, стоящий в файле pom.xml выше, чем jacocoToCobertura-plugin. Визуализация покрытия сохраняется неделю, но затем пропадает.
Ограничения от GitLab: не более 100 источников source в отчете, то есть не более 100 модулей в проекте. Размер одного XML-файла Cobertura не может превышать 10 МБ. Подробнее про ограничения от GitLab тут.
Заключение
Этот плагин выложен на Maven Central, я надеюсь что он сможет сделать ваш merge request чуточку ярче))) Если есть идеи и предложения по улучшению, пишите. Исходники прикреплены.
Авторы
Дмитриев Егор
Митрофанов Сергей
UbuRus
Почему было не взять готовый парсер .exec файлов jacoco, который не требует включения xml репорта?
mvn это хорошо, но если бы сделали еще jar с корной функциональностью, можно было бы написать таск в три строки для gradle, ну или плагин в 10 строк
проект на .ru домене будет достаточно тяжело затащить в компанию (отпугнет многих), было бы лучше использовать com/org координаты