- PVSM.RU - https://www.pvsm.ru -

Оценка тестового покрытия Java проекта на примере Apache Ignite

Я участвую в развитии open source проекта Apache Ignite [1], работая над проектом мне стало интересно оценить тестовое покрытие и вот что из этого получилось.

Оценка тестового покрытия Java проекта на примере Apache Ignite - 1

Покрытие тестами (tests coverage) — наиболее популярная метрика используемая при оценке качества тестирования продукта.

Это одна из немногих метрик, которая позволяет выявить зоны требующие внимания из-за риска пропуска ошибки, а также выполнить приоритизацию работ по модулям или компонентам проекта.

Наиболее простой способ получить полный отчет по оценке тестового покрытия Java [2] проекта — это использовать coverage runner, встроенный в IntelliJ IDEA [3]. Он позволяет в пару кликов настроить сбор метрик и запустить тесты с последующей генерацией отчета.

Тестирование в проекте Apache Ignite

В проекте Apache Ignite [1] для тестирования используется собственный тестовый фреймворк, реализованный на базе JUnit 3. На момент написания статьи core модуль проекта содержит ~82 тысячи тестов, большинство из которых являются компонентными и требуют поднятия кластера из нескольких узлов, в том числе в разных JVM, с сопутствующей подготовкой окружения.

Стоит отметить, что обеспечение работоспособности столь огромной регрессионной базы — непростая задача. Сообщество постоянно следит за состоянием продукта и исправляет найденные ошибки в рамках инициативы "Make Teamcity Green Again [4]".

Обозначенные особенности проекта не позволяют прогнать все тесты разом в одной JVM по следующим причинам:

  • возможная ошибка OutOfMemoryError;
  • возможный отказ (crash) JVM;
  • возможные взаимные блокировки (deadlocks);
  • невозможность старта теста из-за не остановленного узла в предыдущем тесте;
  • прогон займет трое суток на одном компьютере.

Всё это делает невозможным использование IntelliJ IDEA [3] для получения отчета по всем тестам проекта и требует применения специального подхода к решению задачи.

Подготовка и проведение оценки тестового покрытия

Основываясь на проделанной работе, был выбран наиболее надежный подход для выполнения задачи, содержащий следующие шаги:

  1. Определение набора тестовых классов;
  2. Выполнение для каждого тестового класса:
    2.1. запуска и прогона набора тестов класса в отдельной JVM со сторожевым таймером, который завершит поток, в случае зависания или проблем с тестами;
    2.2. операций по получению и сохранению метрик тестового покрытия;
    2.3. очистки окружения по завершении тестов;
  3. Слияние всех метрик полученных в пункте 2;
  4. Генерация полного отчета.

Существует множество инструментов предназначенных для оценки тестового покрытия, наиболее популярные из них:

Не буду останавливаться на их различиях, наглядная таблица сравнения возможностей инструментов для оценки тестового покрытия представлена здесь [8].

Для решения задачи была выбрана библиотека JaCoCo [5], для того, чтобы иметь возможность встроить решение на TeamCity [9], на которой базируется существующая инфраструктура тестирования проекта Apache Ignite [1]. TeamCity [9] умеет "из коробки" работать с JaCoCo [5].

Для автоматизации описанного алгоритма использовались bash-скрипт и Maven [10]. Конфигурация Jacoco Maven плагина [11] реализована отдельным Maven [10] профилем в pom.xml.

Профиль конфигурации JaCoCo [5] плагина приведен ниже и подразумевает разделение на 2 отдельных запуска:

  1. Прогон тестов с подключенным агентом JaCoCo [5] (prepare-agent [12]) для сбора метрик тестового покрытия. Свойство 'runDirectory' будет передаваться скриптом при запуске, что позволит сохранять результаты прогонов изолировано;
  2. Слияние результатов прогона (merge [13]) и генерация отчета (report [14]).

Maven конфигурация JaCoCo

<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>

Ниже приведен скрипт реализующий описанные ранее шаги.

Управляющий bash-cкрипт

#!/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 $''; 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 [15].

Описанный подход легко может быть распараллелен на несколько стендов, при наличии ресурсов, что существенно сократит время прогона и получения оценки тестового покрытия. После встраивания данного решения на TeamCity [9] время оценки тестового покрытия должно занимать около 2-х часов.

Результаты

По результатам отчета, покрытие инструкций [16] проекта составляет ~61%.

Покрытие инструкций основных компонентов:

  • Cache – 66%
  • Discovery — 57%
  • Compute – 60 %
  • Stream – 51 %
  • Binary – 68 %
  • Transactions – 71%

После анализа результатов стало очевидно, что покрыт весь горячий код, а также код по исправлению типовых проблем. Имея такой инструментарий можно будет расширять покрытие на редкие и нетипичные ситуации, делая продукт еще надежнее.

P.S. Полный отчет [17] для ревизии [18].

Автор: daradurvs

Источник [19]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/maven/282301

Ссылки в тексте:

[1] Apache Ignite: https://ignite.apache.org/

[2] Java: https://java.com

[3] IntelliJ IDEA: https://www.jetbrains.com/idea/

[4] Make Teamcity Green Again: https://cwiki.apache.org/confluence/display/IGNITE/Make+Teamcity+Green+Again

[5] JaCoCo: https://github.com/jacoco/jacoco

[6] Cobertura: https://github.com/cobertura/cobertura

[7] Clover: https://bitbucket.org/atlassian/clover

[8] здесь: https://confluence.atlassian.com/clover/comparison-of-code-coverage-tools-681706101.html

[9] TeamCity: https://www.jetbrains.com/teamcity/

[10] Maven: https://maven.apache.org/

[11] Jacoco Maven плагина: https://www.eclemma.org/jacoco/trunk/doc/maven.html

[12] prepare-agent: https://www.eclemma.org/jacoco/trunk/doc/prepare-agent-mojo.html

[13] merge: https://www.eclemma.org/jacoco/trunk/doc/merge-mojo.html

[14] report: https://www.eclemma.org/jacoco/trunk/doc/report-mojo.html

[15] Ubuntu x64 16.04: http://releases.ubuntu.com/16.04/

[16] покрытие инструкций: https://www.eclemma.org/jacoco/trunk/doc/counters.html

[17] Полный отчет: https://www.dropbox.com/s/rdgs1svvojm757x/ignite-2.5-core-module-tests-coverage-report-rev-d83f1ec.zip?dl=0

[18] ревизии: https://github.com/apache/ignite/tree/d83f1ec0a4d010e57fecc12ffd3d1f8346ded61c

[19] Источник: https://habr.com/post/413587/?utm_campaign=413587