NetBeans,Jenkins,Jacoco和Sonar如何在Java8下做持续集成?

引子

Java8已经尘埃落定,它承诺的各种变革最终也都发布了,我相信很多人脑子里都会有这样的疑问:我要不要在项目中使用Java8呢?

实际上,几个月以来我也有同样的问题,现在我将把我对这个问题的答案跟大家分享下。有很多方面的因素会影响这个决定,但是本文中,我主要是关注一个方面的因素:

用Java8以后,我是否能够继续在NetBeans平台上做持续集成?

主要的问题围绕在做持续集成所需要的工具的成熟度和把这些工具跟NetBeans平台上的ant构建脚本集成起来的难易程度。

很幸运的是,我发现这是可能的,并且很容易实现。

同时也非常感谢Alberto Requena Sanchez对本文所做的贡献。

技术环境

一个以安全性和质量为主要驱动力的项目中,持续集成是非常重要的。

基于这个原因,我和我的团队开始了一个叫做 “proof of concept” 的项目,下面列出了要使用的技术:

  • Java8,NetBeans8.0和Ant
  • JUnit4和Jacoco 0.7.1
  • Jenkins和Sonar 4.2

本文主要是解释在Java8环境下,安装设置必要的工具来创建一个完整的能工作的持续集成的服务器的所有步骤。需要注意的是,例子是在Window7平台平台上完成的,但是在Linux上同样也很简单。

下图从高层展示了本文要介绍的体系结构。

持续集成图

Java 8,NetBeans 8.0和Ant

Java 8 已经发布了, 点击下载,安装,学习一下(当然更好),然后开始使用!

我们使用NetBeans8.0来创建模块(modular)应用。这个应用是个多层结构,每一层是由多个模块组成的套件(Suite)组成的,最终的可执行程序是集成到一块的多个Suite。

我们使用Ant来构建工程,但是,如果你是用Maven,这个过程可以更简化,因为Jenkins里面的Sonar集成可以通过Maven的一个插件来完成。

JUnit 4和Jacoco 0.7.1

很显然的,我们会做单元测试,因此我们使用JUnit4,它可以跟任何第三方工具做很好的集成,尤其是NetBeans。

Jacoco是一个非常棒的产生代码覆盖率的工具,而且版本0.7.1已经完全支持Java8.

Jenkins和Sonar 4.2

Jenkins 是持续集成的发动机,它要把上面介绍的所有的工具不出一点差错的集成起来,本文用的版本是1.554。

Sonar用来做代码质量分析。4.2版本已经跟Java8完全兼容。

Ant中使用Sonar需要一个包含了集成到Jenkins的小的类库。如果你使用Maven可以直接安装对应的Maven插件。

现在正式开始

第1步 – NetBeans

1.安装Java 8和NetBeans 8.0。
2.用多个module、类、JUnit测试创建一个suite。
3.把代码提交到代码版本管理工具服务器。
4.进入NetBeans的harness目录。
5.在harness目录下新建文件夹“jacoco-0.7.1″,包含了下载的jacoco的jar文件。
6.在harness目录下新建文件夹“sonar-ant-task”,把下载的sonar和ant的jar文件放进去。
7.在harness目录下新建文件”sonar-jacoco-module.xml”,把以下内容粘贴进去:

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

这个文件的作用是重写junit的测试任务,添加jacoco覆盖,然后复制suite中每一个module的单元测试的结果,这样sonar就可以找到它们并做分析。

8.在harness目录下新建文件”sonar-jacoco-suite.xml”,把以下内容粘贴进去:

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

这个文件的作用是定义suite的级别,sonar的配置和sonar的ant任务。如果你使用的是特殊的数据库或者是用户,你要在这里更改一下配置。

这里面定义的另一个任务是jacoco合并,它会接收每个module的执行结果,然后在构建suite的时候把它们合并成一个结果,这样sonar就可以做分析了。

9.把每个module的build.xml的内容改成:

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

10.把每个suite的build.xml的内容改成:

<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步 – Jenkins

11.通过”管理Jenkins(Manage Jenkins)->管理插件(Manage Plugins)”进入可用的插件列表,安装(如果没有安装的话)下面的插件:

  • JaCoCo
  • Mercurial or Subversion
  • Sonar

如果你要通过防火墙或者是代理连网,无法正常配置网络,你可以在这里手动下载安装。本例中,记得首先要下载插件的依赖插件。

12.通过”管理Jenkins(Manage Jenkins) -> 配置系统(Configure System)”检查插件是否安装正确安装,下面是示例截图(要替换成你自己的文件夹):

13.创建一个新的无样式工程,按你的喜好配置版本控制,在”构建(Build)”面板添加下面3个”Invoce Ant”任务:

14.Finally in the “Post-build Actions” panel add a new “Record Jacoco Coverage Report” configured like this one:

最后在”构建后动作(Post-build Actions)”面板中,按如下设置添加一个新的”记录Jacoco覆盖报表(Record Jacoco Coverage Report)”:

第3步 – Sonar

15.使用下面的脚本创建数据库,可以执行这个查询来确认连接:

GRANT ALL PRIVILEGES ON 'sonar'.* TO 'sonar'@'localhost';

16.打开sonar的配置文件(sonar.properties),设置启用MySQL,这个文件位于安装的conf目录:

# 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

17.在sonar的配置中,更新java插件可以跟Java8保持兼容。

18.总是在sonar.properties文件中配置代理,如果需要的话。

完活!

现在一切都准备好了,你可以在NetBeans中做构建,提交代码,然后Jenkins会执行构建,如果构建成功,会在Sonar中做检查。

以后就是本文的所有内容。希望我没有什么遗漏,但是,如果在以上过程中发现了什么错误,别犹豫回复本文,我会努力找出解决办法的。

原文链接: javacodegeeks 翻译: ImportNew.com - miracle1919
译文链接: http://www.importnew.com/11354.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部