Я участвую в развитии open source проекта Apache Ignite, работая над проектом мне стало интересно оценить тестовое покрытие и вот что из этого получилось.
Покрытие тестами (tests coverage) — наиболее популярная метрика используемая при оценке качества тестирования продукта.
Это одна из немногих метрик, которая позволяет выявить зоны требующие внимания из-за риска пропуска ошибки, а также выполнить приоритизацию работ по модулям или компонентам проекта.
Наиболее простой способ получить полный отчет по оценке тестового покрытия Java проекта — это использовать coverage runner, встроенный в IntelliJ IDEA. Он позволяет в пару кликов настроить сбор метрик и запустить тесты с последующей генерацией отчета.
В проекте Apache Ignite для тестирования используется собственный тестовый фреймворк, реализованный на базе JUnit 3. На момент написания статьи core модуль проекта содержит ~82 тысячи тестов, большинство из которых являются компонентными и требуют поднятия кластера из нескольких узлов, в том числе в разных JVM, с сопутствующей подготовкой окружения.
Стоит отметить, что обеспечение работоспособности столь огромной регрессионной базы — непростая задача. Сообщество постоянно следит за состоянием продукта и исправляет найденные ошибки в рамках инициативы "Make Teamcity Green Again".
Обозначенные особенности проекта не позволяют прогнать все тесты разом в одной JVM по следующим причинам:
Всё это делает невозможным использование IntelliJ IDEA для получения отчета по всем тестам проекта и требует применения специального подхода к решению задачи.
Основываясь на проделанной работе, был выбран наиболее надежный подход для выполнения задачи, содержащий следующие шаги:
Существует множество инструментов предназначенных для оценки тестового покрытия, наиболее популярные из них:
Не буду останавливаться на их различиях, наглядная таблица сравнения возможностей инструментов для оценки тестового покрытия представлена здесь.
Для решения задачи была выбрана библиотека JaCoCo, для того, чтобы иметь возможность встроить решение на TeamCity, на которой базируется существующая инфраструктура тестирования проекта Apache Ignite. TeamCity умеет "из коробки" работать с JaCoCo.
Для автоматизации описанного алгоритма использовались bash-скрипт и Maven. Конфигурация Jacoco Maven плагина реализована отдельным Maven профилем в pom.xml.
Профиль конфигурации JaCoCo плагина приведен ниже и подразумевает разделение на 2 отдельных запуска:
<profile>
<id>coverage</id>
<properties>
<argLine>
-ea -server -Xms1g -Xmx6g -XX:+HeapDumpOnOutOfMemoryError -XX:+AggressiveOpts -DIGNITE_UPDATE_NOTIFIER=false -DIGNITE_NO_DISCO_ORDER=true -DIGNITE_PERFORMANCE_SUGGESTIONS_DISABLED=true -DIGNITE_QUIET=false -Djava.net.preferIPv4Stack=true </argLine>
<coverage.dataFile>${runDirectory}/coverage-reports/jacoco-ut.exec</coverage.dataFile>
<coverage.outputDir>${runDirectory}/jacoco-ut</coverage.outputDir>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
</configuration>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.1</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<destFile>${coverage.dataFile}</destFile>
</configuration>
</execution>
<execution>
<id>post-merge</id>
<phase>validate</phase>
<goals>
<goal>merge</goal>
</goals>
<configuration>
<fileSets>
<fileSet>
<directory>${basedir}</directory>
<includes>
<include>results/*/coverage-reports/jacoco-ut.exec</include>
</includes>
</fileSet>
</fileSets>
<destFile>merged.exe</destFile>
</configuration>
</execution>
<execution>
<id>generate-report</id>
<phase>validate</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${basedir}/merged.exe</dataFile>
<outputDirectory>${basedir}/coverage-report</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Ниже приведен скрипт реализующий описанные ранее шаги.
#!/bin/bash
# Проект должен быть скомпилирован в соответствии с DEVNOTES.txt
#
# Скрипт необходимо запускать в: ignite/modules/core
#
# Команда запуска скрипта: 'nohup ./coverage.sh >/dev/null 2>&1 &'
SCRIPT_DIR=$(cd $(dirname "$0"); pwd)
echo "***** Старт."
echo "***** Поиск тестовых классов..."
tests=()
while IFS= read -r -d $'\0'; do
tests+=("$REPLY")
done < <(find $SCRIPT_DIR/src/test/java/org/apache/ignite -type f -name "*Test*" ! -name "*\$*" ! -name "*Abstract*" ! -name "*TestSuite*" -print0)
testsCount=${#tests[@]}
echo "***** Количество тестовых классов="$testsCount
idx=0
for path in ${tests[@]}
do
idx=$((idx+1))
echo "***** Запуск "$idx" из "$testsCount
echo "***** Расположение класса: "$path
filename=$(basename -- "$path")
filename="${filename%.*}"
echo "***** Название класса: "$filename
runDir=$SCRIPT_DIR"/results/"$filename
mkdir -p $runDir
if [ "$(ls -A $runDir)" ]; then
continue
fi
echo "***** Запуск тестов..."
timeout 30m mvn -P surefire-fork-count-1,coverage test -Dmaven.main.skip=true -Dmaven.test.failure.ignore=true -Dtest=$filename -DfailIFNoTests=false -DrunDirectory=$runDir
echo "***** Очистка окружения..."
pkill java
done
# Объединение результатов и генерация отчета
mvn -X -P surefire-fork-count-1,coverage validate
echo "***** Финиш."
Прогон всех тестов с оценкой покрытия занял ~50 часов на выделенном сервере: 4 vCPU, 8RAM, 50 SSD, Ubuntu x64 16.04.
Описанный подход легко может быть распараллелен на несколько стендов, при наличии ресурсов, что существенно сократит время прогона и получения оценки тестового покрытия. После встраивания данного решения на TeamCity время оценки тестового покрытия должно занимать около 2-х часов.
По результатам отчета, покрытие инструкций проекта составляет ~61%.
Покрытие инструкций основных компонентов:
После анализа результатов стало очевидно, что покрыт весь горячий код, а также код по исправлению типовых проблем. Имея такой инструментарий можно будет расширять покрытие на редкие и нетипичные ситуации, делая продукт еще надежнее.
P.S. Полный отчет для ревизии.
К сожалению, не доступен сервер mySQL