Статьи

Как сделать непрерывную интеграцию с Java 8, платформой NetBeans 8, Jenkins, Jacoco и Sonar

вступление

Java 8 есть, обещанная революция наконец выпущена, и я уверен, что многие из вас имеют в виду один и тот же вопрос «Должен ли я использовать его в своем проекте?».

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

Могу ли я продолжать непрерывную интеграцию с Java 8 и платформой NetBeans?

Основной вопрос заключался в зрелости инструментов, необходимых для выполнения CI, и в том, насколько легко было интегрировать это со сценариями сборки ant платформы NetBeans.

К счастью, мы обнаружили, что это возможно и легко сделать!

Я также хотел бы поблагодарить Альберто Рекена Санчес за его вклад в эту статью.

Техническая среда

Работая в проекте, где безопасность и качество являются главными факторами, КИ жизненно важен.

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

  • Java 8, NetBeans 8.0 и Ant
  • JUnit 4 & Jacoco 0.7.1
  • Jenkins & Sonar 4.2

Цель этого поста — объяснить все шаги, сделанные для установки и настройки необходимых инструментов, чтобы иметь полностью работающий CI-сервер для Java 8. Обратите внимание, что доказательство было сделано на компьютере разработчика в Windows 7, но это легко сделать. то же самое на сервере Linux.

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

Диаграмма непрерывной интеграции

Диаграмма непрерывной интеграции

Java 8, NetBeans 8.0 и Ant

Выпущена Java 8 , получите ее здесь, установите, изучите (желательно) и начните использовать!

Мы используем платформу NetBeans 8.0 для создания модульного приложения. Это приложение имеет многоуровневую архитектуру, где каждый слой представляет собой набор модулей, а конечный исполняемый файл представляет собой просто интегрированный набор наборов.

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

JUnit 4 & Jacoco 0.7.1

Естественно, мы проводим модульные тесты, и по этой причине мы используем JUnit 4. Он хорошо интегрирован везде, особенно в NetBeans.

Jacoco — отличный инструмент для генерации покрытия кода, а начиная с версии 0.7.1 он полностью поддерживает Java 8.

Jenkins & Sonar 4.2

Jenkins — это движок нашего CI-сервера, он без проблем интегрируется со всеми вышеописанными технологиями. Протестированная версия 1.554.

Сонар проводит весь анализ качества кода. Релиз 4.2 имеет полную совместимость с Java 8.

Для использования Sonar с Ant необходима небольшая библиотека, которая содержит цель для интеграции в Jenkins. Если вы используете Maven вместо этого, вы можете просто установить плагин для Maven .

Начиная головоломку

Шаг 1 — NetBeans

  1. Установите Java 8 и NetBeans 8.0
  2. Создайте набор модулей с несколькими модулями, несколькими классами и несколькими тестами jUnit.
  3. Зафиксируйте код на вашем сервере управления версиями исходного кода
  4. Внутри жгута NetBeans
  5. Создайте папку в жгуте с именем «jacoco-0.7.1», содержащую загруженные jacoco jars
  6. Создайте в жгуте папку «sonar-ant-task» и поместите в скачанные фляги sonar ant.
  7. Создайте файл в жгуте с именем sonar-jacoco-module.xml и вставьте в него следующий код:
  8. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    <?xml version="1.0" encoding="UTF-8"?>
    <!--
     
    -->
    <project name="sonar-jacoco-module" basedir="." xmlns:jacoco="antlib:org.jacoco.ant" xmlns:sonar="antlib:org.sonar.ant">
    <description>Builds the module suite otherSuite.</description>
     
    <property name="jacoco.dir" location="${nbplatform.default.harness.dir}/jacoco-0.7.1"/>
    <property name="result.exec.file" location="${jacoco.dir}/jacoco.exec"/>
    <property name="build.test.results.dir" location="build/test/unit/results"/>
     
    <property file="nbproject/project.properties"/>
     
    <!-- Step 1: Import JaCoCo Ant tasks -->
    <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
    <classpath path="${jacoco.dir}/jacocoant.jar"/>
    </taskdef>
     
    <!-- Target at the level of modules -->
    <target name="-do-junit" depends="test-init">
    <echo message="Doing testing for jacoco" />
    <macrodef name="junit-impl">
    <attribute name="test.type"/>
    <attribute name="disable.apple.ui" default="false"/>
    <sequential>
    <jacoco:coverage destfile="${build.test.results.dir}/${code.name.base}_jacoco.exec">
    <junit showoutput="true" fork="true" failureproperty="tests.failed" errorproperty="tests.failed"
    filtertrace="${test.filter.trace}" tempdir="${build.test.@{test.type}.results.dir}" timeout="${test.timeout}">
    <batchtest todir="${build.test.@{test.type}.results.dir}">
    <fileset dir="${build.test.@{test.type}.classes.dir}" includes="${test.includes}" excludes="${test.excludes}"/>
    </batchtest>
    <classpath refid="test.@{test.type}.run.cp"/>
    <syspropertyset refid="test.@{test.type}.properties"/>
    <jvmarg value="${test.bootclasspath.prepend.args}"/>
    <jvmarg line="${test.run.args}"/>
    <!--needed to have tests NOT to steal focus when running, works in latest apple jdk update only.-->
    <sysproperty key="apple.awt.UIElement" value="@{disable.apple.ui}"/>
    <formatter type="brief" usefile="false"/>
    <formatter type="xml"/>
    </junit>
    </jacoco:coverage>
    <copy file="${build.test.results.dir}/${code.name.base}_jacoco.exec" todir="${suite.dir}/build/coverage"/>
    <!--
    Copy the result of all the unit tests of all the modules into one common
    folder at the level of the suite, so that sonar could find those files to
    generate associated reports
    -->
    <copy todir="${suite.dir}/build/test-results">
    <fileset dir="${build.test.results.dir}">
    <include name="**/TEST*.xml"/>
    </fileset>
    </copy>
    <fail if="tests.failed" unless="continue.after.failing.tests">Some tests failed; see details above.</fail>
    </sequential>
    </macrodef>
    <junit-impl test.type="${run.test.type}" disable.apple.ui="${disable.apple.ui}"/>
    </target>
     
    </project>

    Задача этого файла — переопределить задачу do-junit, добавив покрытие jacoco, и скопировать результат модульного тестирования каждого модуля в сборке комплекта, чтобы сонар нашел их все вместе для выполнения своего анализа.

  9. Создайте файл в жгуте с именем sonar-jacoco-suite.xml и вставьте в него следующий код
  10. 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    <?xml version="1.0" encoding="UTF-8"?>
    <project name="sonar-jacoco-suite" basedir="." xmlns:jacoco="antlib:org.jacoco.ant" xmlns:sonar="antlib:org.sonar.ant">
    <description>Builds the module suite otherSuite.</description>
     
    <property name="jacoco.dir" location="${nbplatform.default.harness.dir}/jacoco-0.7.1"/>
    <property name="result.exec.file" location="build/coverage"/>    
     
    <!-- Define the SonarQube global properties (the most usual way is to pass these properties via the command line) -->
    <property name="sonar.jdbc.url" value="jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8" />
    <property name="sonar.jdbc.username" value="sonar" />
    <property name="sonar.jdbc.password" value="sonar" />
    <!-- Define the SonarQube project properties -->
    <property name="sonar.projectKey" value="org.codehaus.sonar:example-java-ant" />
    <property name="sonar.projectName" value="Simple Java Project analyzed with the SonarQube Ant Task" />
    <property name="sonar.projectVersion" value="1.0" />
    <property name="sonar.language" value="java" />
    <!-- Load the project properties file for retrieving the modules of the suite -->
    <property file="nbproject/project.properties"/>
     
    <!-- Using Javascript functions to build the paths of the data source for sonar configuration -->
    <script language="javascript"
    <![CDATA[
     
    // getting the value
    modulesName = project.getProperty("modules");
    modulesName = modulesName.replace(":",",");
    res = modulesName.split(",");
    srcModules = "";
    binariesModules = "";
    testModules = "";
    //Build the paths  
    for (var i=0; i<res.length; i++)
    {
    srcModules += res[i]+"/src,";
    binariesModules += res[i]+"/build/classes,";
    testModules += res[i]+"/test,";
    }
    //Remove the last comma
    srcModules = srcModules.substring(0, srcModules.length - 1);
    binariesModules = binariesModules.substring(0, binariesModules.length - 1);
    testModules = testModules.substring(0, testModules.length - 1);
    // store the result in a new properties
    project.setProperty("srcModulesPath",srcModules);
    project.setProperty("binariesModulesPath",binariesModules);
    project.setProperty("testModulesPath",testModules);
    ]]>
    </script>  
    <!-- Display the values -->       
    <property name="sonar.sources" value="${srcModulesPath}"/>
    <property name="sonar.binaries" value="${binariesModulesPath}" />
    <property name="sonar.tests" value="${testModulesPath}" />
    <!-- Define where the coverage reports are located -->
    <!-- Tells SonarQube to reuse existing reports for unit tests execution and coverage reports -->
    <property name="sonar.dynamicAnalysis" value="reuseReports" />
    <!-- Tells SonarQube where the unit tests execution reports are -->
    <property name="sonar.junit.reportsPath" value="build/test-results" />
    <!-- Tells SonarQube that the code coverage tool by unit tests is JaCoCo -->
    <property name="sonar.java.coveragePlugin" value="jacoco" />
    <!-- Tells SonarQube where the unit tests code coverage report is -->
    <property name="sonar.jacoco.reportPath" value="${result.exec.file}/merged.exec" />
    <!--  Step 1: Import JaCoCo Ant tasks  -->
    <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
    <classpath path="${jacoco.dir}/jacocoant.jar"/>
    </taskdef>    
    <target name="merge-coverage">        
    <jacoco:merge destfile="${result.exec.file}/merged.exec">
    <fileset dir="${result.exec.file}" includes="*.exec"/>
    </jacoco:merge>
    </target>
     
    <target name="sonar">
    <taskdef uri="antlib:org.sonar.ant" resource="org/sonar/ant/antlib.xml">
    <!-- Update the following line, or put the "sonar-ant-task-*.jar" file in your "$HOME/.ant/lib" folder -->
    <classpath path="${harness.dir}/sonar-ant-task-2.1/sonar-ant-task-2.1.jar" />
    </taskdef>
     
    <!-- Execute the SonarQube analysis -->
    <sonar:sonar />
    </target>
     
    </project>

    Задача этого файла — определить на уровне набора конфигурацию сонара и задачу муравья сонара. Если вы используете для сонара какую-то специальную базу данных или специальных пользователей, вы должны изменить конфигурацию.

    Другая заданная задача — это слияние jacoco, которое фактически возьмет все сгенерированные exec для каждого модуля и объединит их в один exec при сборке комплекта, чтобы позволить сонару выполнить свой анализ.

  11. Замените содержимое build.xml каждого модуля следующим:
  12. 1
    2
    3
    4
    5
    6
    <description>Builds, tests, and runs the project com.infrabel.jacoco.</description>
    <property file="nbproject/suite.properties"/>
    <property file="${suite.dir}/nbproject/private/platform-private.properties"/>
    <property file="${user.properties.file}"/>
    <import file="${nbplatform.default.harness.dir}/sonar-jacoco-module.xml"/>
    <import file="nbproject/build-impl.xml"/>
  13. Замените содержимое build.xml каждого пакета следующим:
  14. 1
    2
    3
    4
    5
    <description>Builds the module suite otherSuite.</description>
    <property file="nbproject/private/platform-private.properties"/>
    <property file="${user.properties.file}"/>
    <import file="${nbplatform.default.harness.dir}/sonar-jacoco-suite.xml"/>
    <import file="nbproject/build-impl.xml"/>

    Шаг 2 — Дженкинс

  15. В «Управление Jenkins -> Управление плагинами» перейдите в список доступных и установите (если он еще не установлен) следующие плагины:
  • JaCoCo
  • Mercurial или Subversion
  • сонар

Если вы находитесь за брандмауэром или прокси-сервером и получаете проблемы с настройкой сетевых параметров, вы всегда можете скачать и установить их вручную отсюда . В этом случае не забудьте сначала загрузить также зависимости каждого плагина.

  • В разделе «Управление Jenkins -> Настроить систему» ​​убедитесь, что все плагины настроены правильно, см. На следующих скриншотах пример (замените папки на те, которые вам больше подходят):
  • Дженкинс-JDK1

    Дженкинс-JDK

    Jenkins-гидролокатора-1

    Jenkins-гидролокатора-2

  • Создайте новый проект свободного стиля, настройте управление версиями по своему вкусу и на панели «Построение» добавьте следующие три задачи «Invoce Ant»:
  • Дженкинс конфигурировать

  • Наконец, на панели «Действия после сборки» добавьте новый «Отчет о покрытии Jacoco», настроенный так:
  • Дженкинс-jacoco

    Шаг 3 — Сонар

  • Создайте базу данных, следуя этому сценарию , и при необходимости запустите этот запрос, чтобы соединение работало:
  • 1
    GRANT ALL PRIVILEGES ON 'sonar'.* TO 'sonar'@'localhost';
  • Зайдите в конфигурационный файл sonar (sonar.properties) и включите использование MySQL, файл находится в папке conf установки
  • 01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    # Permissions to create tables, indices and triggers
    # must be granted to JDBC user.
    # The schema must be created first.
    sonar.jdbc.username=sonar
    sonar.jdbc.password=sonar
     
    #----- MySQL 5.x
    # Comment the embedded database and uncomment the following
    # line to use MySQL
    sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
  • В конфигурации сонара обновите плагин Java, если необходимо, чтобы он был совместим с Java 8
  • При необходимости зайдите и настройте свой прокси всегда в файле sonar.properties
  • Готово!

    Теперь все настроено, вы можете зайти в NetBeans, выполнить сборку, зафиксировать свой код, затем в Jenkins запустить сборку и после завершения сборки проверить проект в Sonar.

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