Удобная подсветка покрытия кода тестами в Merge Request GitLab
Проблема
Основным средством для командной работы с репозиторием компании, в которой я работаю, является 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 чуточку ярче))) Если есть идеи и предложения по улучшению, пишите. Исходники прикреплены.
Авторы
Дмитриев Егор
Митрофанов Сергей