четверг, 13 октября 2011 г.

Обнаружим подводные камни быдлокода Sonar'ом

Все началось с простой задачи - прикрутить к мавен билду отчет о покрытии кода тестами. Погуглил.

Первым всплыла Emma. Плагин для эклипсика поставился быстро и отлично работал - можно было быстро увидеть покрытие кода конкретным тестом. Отличная фича для повседневной работы и быстрой проверки. Но по прежнему оставалась основная задача - узнать покрытие кода всеми тестами в мавен проекте.

И вот с использованием Emma в мавене возник серьезный затык. В распоряжении были три плагина:
У первых двух были подозрительно одинаковые goal-ы, возможно представляют собой одно и то же. Возможности очень узкие, документация беднейшая (для сонатапойвского все что можно было найти - это указанную по ссылке статью, в которой в примерах конфигов xml теги съедены). В итоге все попытки настроить плагины превращаются в битье головой об стену. Про поддержку покрытия межмодульного тестирования и говорить не приходится.
Третий плагин чрезвычайно стар - написан аж для первого мавена. Для написания плагина под второй мавен по словам разработчиков "there is some interest". Ок, вопросов не имеем.

Погоревав немного над унылостью еммо-поддержки в мавене, я смягчил свое сердце по отношению к ранее нагугленной платформе для комплексного анализа кода Sonar.
"Мне всего лишь нужно покрытие кода тестами, зачем ставить целую отдельную платформу?" - думал я раньше.
Но каково же было мое удивление, когда я смог за считанные минуты загрузить сонар, проанализировать с его помощью весь проект, и получить помимо покрытия кода юнит тестами всевозможные указания на проблемные места быдлокода и быдлодизайна.

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

Далее уже залогинившись в систему сонара под дефолтным акком admin/admin сделал следующее:
  • поменял пароль данного аккаунта на... кхм... на секретное значение (Administrator -> My Profile)
  • выставил в качестве дефолтного профиля оценки качества кода 'Sonar way with Findbugs', так как изначальный профиль 'Sonar way' содержит намного меньшую базу данных косяков (Configuration -> Quality Profiles)
  • ВАЖНО! в сонаре имеется возможность просмотра исходников кода, поэтому если проект закрытый, необходимо настроить обязательный вход под каким-либо из используемых аккаунтов, т.е. запретить анонимусам смотреть что-либо в сонаре (Configuration -> General Settings -> Security -> Force user authentication)
  • поставил няшные плагины: Clirr, JaCoCo, Motion Chart, SIG Maintainability Model, Taglist, Timeline, Total Quality (установить и посмотреть описание всех плагинов можно в Configuration -> Update Center -> Available Plugins) Из них уже успел убедится в полезности JaCoCo и Timeline,первый добавляет поддержку JaCoCo - альтернативного дефолтной Cobertura тула для анализа покрытия кода тестами (о нем чуть ниже), второй позволяет заценить динамику изменения параметров качества кода во времени
  • настроил использование JaCoCo в своем проекте. Выбор JaCoCo был сделан из-за его возможности отражать покрытие как юнит тестов так и интеграционных тестов(читай тестов, использующих межмодульные связи). Пришлось немного повозиться. Настройки делал для мультимодульного проекта, имеющего родительский пом, в каждом модуле имелись юнит тесты, кроме того в отдельных модулях имелись тесты, использующие классы из других модулей.
    1. Сначала в самом сонаре ставим JaCoCo как дефолтный аналитик покрытия (Configuration -> General Settings -> Code Coverage -> Code coverage plugin = jacoco). Можно это не выставлять здесь. Но тогда придется при вызове мавена указывать значение в виде параметра.
    2. Настраиваем surefire плагин в родительском pom.xml чтобы он мог принимать параметры JVM:
      <project>
         ...
         <build>
            ...
            <plugins>
               ...
               <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-surefire-plugin</artifactId>
                  <configuration>
                     <argLine>${surefire.jvm.args}</argLine>
                  </configuration>
                  <inherited>true</inherited>
               </plugin>
            </plugins>
         </build>
      </project>
      
      
    3. Теперь начинается сам процесс. Он у нас состоит из двух шагов: прокатка билда с паралельным сбором информации о покрытии тестов и проход сонаром по проекту. Все команды выполняем, находясь в папке родительского мавен проекта.
    4. Шаг первый: билдим проект и собираем инфу о покрытии. Для этого нам необходимо выполнять тесты специальным JaCoCo агентом. Команда принимает следующий вид:
      JACOCO_FILE="jacoco.exec"
      JACOCO_DUMP="target/$JACOCO_FILE"
      JACOCO_AGENT="$MAVEN_REPO/repository/org/jacoco/org.jacoco.agent/0.5.3.201107060350/org.jacoco.agent-0.5.3.201107060350-runtime.jar"
      
      mvn -Dsurefire.jvm.args=-javaagent:$JACOCO_AGENT=destfile=$JACOCO_DUMP clean install
      
      Небольшие пояснения. Параметр surefire.jvm.args мы только что использовали при настройке Surefire, с помощью его значения мы говорим "эй, surefire! используй jacoco агент отсюда, а свои результаты сохраняй вот сюда". В моем примере я использую агент из мавен репозитория, но можно его и отдельно качнуть. Путь к сохраняемому файлу указан относительный благодаря чему для каждого модуля получится свой джакоко файл.
    5. Шаг полуторный. Прежде чем начнем сбор инфы для сонара необходимо сделать одну маленькую вещь - создать дополнительный джакоко файл, содержащий в себе все джакоко файлы модулей. Это позволит нам проанализировать межмодульное покрытие кода тестами. Если быть более точным, то в получившемся файле будет информация о покрытии любым тестом - как локальным юнит тестом так и тестом из другого модуля. Делаем:
      JACOCO_DUMP_IT="/tmp/$JACOCO_FILE"
      cat `find -name $JACOCO_FILE` > $JACOCO_DUMP_IT
      
      Как видно объединение представляет собой банальную конкатенацию.
    6. Ну и теперь второй шаг - вызываем сбор инфы для сонара
      SONAR_URL="http://localhost:9000"
      DB_DRIVER="org.postgresql.Driver"
      DB_URL="jdbc:postgresql://localhost:5432/sonar"
      DB_USER="user"
      DB_PASSWORD="password"
      
      mvn -e sonar:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.jdbc.driver=$DB_DRIVER -Dsonar.jdbc.url=$DB_URL -Dsonar.jdbc.username=$DB_USER -Dsonar.jdbc.password=$DB_PASSWORD -Dsonar.jacoco.itReportPath=$JACOCO_DUMP_IT -Dmaven.test.skip -Dsonar.dynamicAnalysis=reuseReports
      
      Параметр sonar.jacoco.itReportPath - то что и позволяет нам разделить информацию о покрытии юнит тестами и тестами вообще. В своем примере я использую эту возможность не совсем по назначению. Предполагается что у вас имеется отдельный модуль для интеграционных тестов, который собирает все модули воедино и тестит их. Покрытие таких ИТ тестов и проверяется. У меня же интеграционные тесты разбросаны по разным модулям, поэтому мне более полезно будет знать об общем покрытии классов любым тестом неважно из какого модуля. Параметр maven.test.skip позволяет избежать повторного запуска тестов. Значение sonar.dynamicAnalysis=reuseReports указывает сонару не удалять уже имеющиеся после нашего первого шага джакоко файлы.
  • Добавил необходимые виджеты на странице Dashboard ( -> Dashboard -> Configure widgets). Имейте ввиду, что на дашборде ничто само по себе не появляется, после установки какого-либо плагина предполагающего доп. вывод, придется здесь добавить виджет. В частности фича покрытия ИТ тестами от джакоко предполагает добавление виджета.
  • Полазил, посмотрел результаты - ай нрайца! Ай люблю! 
Вот так относительно малой кровью получился полезный тул при разработке.  Можно продолжать накручивать возможности, в частности интегрировать Трак и Сонар, будет выводится какая-то инфа по тикетам. Но мне пока хватит и этого.
Для удобства запуска процесса сбора инфы на проекте советую поместить все команды в скриптец.
Вот весь скрипт целиком:
#! /bin/sh
#
#change parameters to fit your needs
SONAR_URL="http://localhost:9000"
DB_DRIVER="org.postgresql.Driver"
DB_URL="jdbc:postgresql://localhost:5432/sonar"
DB_USER="user"
DB_PASSWORD="password"
JACOCO_FILE="jacoco.exec"
JACOCO_DUMP="target/$JACOCO_FILE"
JACOCO_DUMP_IT="/tmp/$JACOCO_FILE"
MAVEN_REPO="/home/user/.m2"
JACOCO_AGENT="$MAVEN_REPO/repository/org/jacoco/org.jacoco.agent/0.5.3.201107060350/org.jacoco.agent-0.5.3.201107060350-runtime.jar"

mvn -Dsurefire.jvm.args=-javaagent:$JACOCO_AGENT=destfile=$JACOCO_DUMP clean install

cat `find -name $JACOCO_FILE` > $JACOCO_DUMP_IT

mvn -e sonar:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.jdbc.driver=$DB_DRIVER -Dsonar.jdbc.url=$DB_URL -Dsonar.jdbc.username=$DB_USER -Dsonar.jdbc.password=$DB_PASSWORD -Dsonar.jacoco.itReportPath=$JACOCO_DUMP_IT -Dmaven.test.skip -Dsonar.dynamicAnalysis=reuseReports

rm $JACOCO_DUMP_IT

Напоследок ссылочки, из которых была почерпнута информация:
http://docs.codehaus.org/display/SONAR/The+2+minutes+tutorial
http://docs.codehaus.org/display/SONAR/Documentation
http://mojo.codehaus.org/sonar-maven-plugin/sonar-mojo.html
http://www.sonarsource.org/measure-code-coverage-by-integration-tests-with-sonar/
http://docs.codehaus.org/display/SONAR/JaCoCo+Plugin
http://www.eclemma.org/jacoco/trunk/doc/agent.html
http://docs.codehaus.org/display/SONAR/Advanced+parameters