简介: 持续集成(Continuous Integration,CI)是持续地编译、测试、检查和部署源代码的过程。在许多持续集成环境中,这意味着每当源代码管理库中的代码发生改变时,都要执行新的构建。CI 的好处很明确:经常组装软件可以大大提高在早期发现缺陷的可能性,而缺陷在早期还不复杂,容易解决。本教程是追求代码质量系列的配套文章。在本教程中,Andrew Glover 介绍持续集成的基本方面,并讲解如何用最好的开放源码技术设置 CI 过程
开始之前
了解本教程讨论的内容以及如何从本教程获得最大的收益。
本教程讨论持续集成的基本问题:什么是持续集成,为什么需要它,它是如何工作的,以及 CI 环境中的开发步骤。本教程讲解如何设置 CI 过程来建立一个可重复的可靠的构建过程。
您将学习如何正确地配置 CI 服务器,让它查询 SCM 存储库,并在探测到源代码中的修改时运行 Ant 构建过程。还要学习如何运行自动的 JUnit 测试,以及如何用 PMD 和 FindBugs 进行软件检查。最后,体会一下 Hudson(一种出色的 CI 服务器)如何在问题发生时发出通知,最终帮助您更快速地构建可靠的软件。
本教程使用 Hudson、Ant 和 Subversion 作为框架,讲解持续集成的基本概念。在学完这个一小时的教程时,您会理解持续集成的好处,以及如何正确地设置和配置 Hudson、Ant 和 Subversion。产生的构建过程将运行测试和软件检查,并在错误发生时尽快报告。
为了从本教程获得最大的收益,您应该熟悉 Java™ 开发。本教程还假设您理解构建具有适当质量的软件的价值,并熟悉 JUnit。
持续集成环境需要一个自动构建工具、一个代码存储库和一个 CI 服务器。为了实践本教程中的代码,需要安装 Java 平台以及 Hudson 1.150、Ant 1.7、JUnit 3.8.1 和 Subversion 1.4.x。
对于本教程,推荐的系统配置如下:
- 一个支持 Sun JDK 1.5.0_09(或更高版本)或 IBM Developer Kit for Java 1.5.0 SR3 的系统,至少 500 MB 主内存
- 安装软件组件和示例需要至少 20MB 的硬盘空间
教程中的说明基于 Microsoft® Windows® 操作系统。本教程中的所有工具也可以用在 Linux® 和 Unix® 系统上。
持续集成的核心概念
CI 过程会经常构建软件组件;在许多情况下,每当源代码存储库(比如 Subversion 或 ClearCase)中的代码发生变化时,都要构建软件组件。CI 的好处是:经常构建软件可以确保尽早遇到问题(比如代码缺陷),避免问题在软件开发周期晚期变复杂时才被发现。
尽管 CI 实际上是一个过程,但是持续集成 这个词常常与一个或多个工具相关联。在本教程中,讲解如何安装、配置和使用 Hudson 作为 CI 服务器,但是要记住,CI 远不只是个工具。实际上,使用的工具可能是 CI 比较次要的方面,因为 CI 工具所做的仅仅是在代码存储库中探测到修改时运行构建。构建过程本身比用来运行它的工具重要得多。
开始使用 CI 需要三个组件:
- 用 Ant 或 Maven 等工具建立的自动构建过程
- 一个代码存储库,比如 CVS 或 Subversion
- 一个 CI 服务器,比如 Hudson,但是 cron 作业也可以满足需要
我们来详细讨论这些组件。
CI 过程会经常集成软件,这需要通过构建来完成。在 Java 环境中,Ant 是常用的构建平台。可以使用 Ant 可靠地自动执行编译、测试等任务,甚至可以执行软件检查和部署。在掌握了 CI 的所有组件之后,您会发现构建策略是成功的 CI 过程最重要的方面。如果缺少适当的构建过程,CI 就难以发挥作用。
为了让 CI 正确地发挥作用,需要一个源代码管理(SCM)系统或存储库,比如 Subversion 或 CVS。CI 服务器向 SCM 存储库查询代码修改。在找到修改时,CI 服务器执行签出(即更新本地沙箱)并执行构建。除了执行得更频繁之外,构建过程与在本地环境中执行的构建相同。
对于成功的 CI 过程,需要用一个自动的过程监视 SCM 存储库并在探测到修改时运行构建,这也非常重要。对于 Java 平台,有许多可用的 CI 服务器,包括开放源码软件和商业产品。它们的基本配置都很相似,适合监视特定的 SCM 并在探测到修改时运行构建。所有 CI 服务器都有自己的优缺点。Hudson 尤其让人感兴趣,因为它容易配置而且具有强大的插件,这些插件可以显示测试结果趋势等信息。
Hudson 简介
Hudson 是一种革命性的开放源码 CI 服务器,它从以前的 CI 服务器吸取了许多经验教训。Hudson 最吸引人的特性之一是它很容易配置:很难找到更容易设置的 CI 服务器,也很难找到开箱即用特性如此丰富的 CI 服务器。Hudson 容易使用的第二个原因是它具有强大的插件框架,所以很容易添加特性。例如,一个 Hudson 插件可以随时间的推移跟踪 FindBugs 和代码覆盖。它还可以报告测试结果的趋势(来自 JUnit 或 TestNG)以及构建结果和对应的执行时间。
Hudson 需要运行 Java 5。如果需要使用 Hudson 附带的嵌入式容器(Winstone)之外的其他容器,那么只需使用一种 Servlet 2.4 容器。对于大多数情况,Winstone 就足够了。
开始使用 CI
本节讨论如何把 CI 过程的各个组件组合在一起,以及采用这种方式的原因。在此之后,就可以开始编写代码了!
可重复的可靠构建是可预测的软件过程的基础。正如前面提到的,Ant 是 Java 平台上流行的构建工具,它的主要用途是自动执行编译和部署等常见任务。Ant 还有助于用 JUnit 和 TestNG 等测试框架进行单元测试,而且它可以与许多其他工具集成,比如 PMD 和 FindBugs(用来自动执行静态代码分析)。使用 Ant 编译源文件很容易,只需在命令提示下发出ant compile
命令。
尽管 Ant 有助于保证可重复性,但是可重复性 主要取决于开发人员自己。在软件构建方面,可重复性意味着,不同的开发人员在发出编译或测试等命令时,会得到相同的结果。如果由于某种原因(比如构建本身没有显式引用一个必需的库),一位开发人员无法完成编译,而另一个开发人员可以完成编译,那么构建就是不可靠的,也可以认为它是不可重复的!
可靠性和可重复性对于 CI 非常重要。因为 CI 是一个没有人为干预的自动过程,所以 CI 最终调用的构建过程必须没有瑕疵。构建过程必须是一个简单的活动,不依赖于 CI 服务器无法控制的东西,比如需要精确引用的库、没有明确文档记录的位置和手工刷新的数据库。当然,某些构建操作会失败,例如某人签入了无法编译的代码;但是构建不能由于本身的缺陷而失败。
要想让 CI 能够促进软件开发过程,构建的作用必须不仅仅是编译代码。因为在每次修改代码时都会运行构建,所以这是运行测试和代码检查的好时机。
基本的构建过程包含以下任务:
- 编译源代码,包括测试
- 执行测试,包括用 JUnit 或 TestNG 编写的测试
- 运行代码检查,比如 PMD
- 将最终的产品存档为 JAR、WAR 或一系列文件
- 部署最终的产品(假设最终产品允许部署)
请记住,这些是最基本的步骤,构建过程可能包含更高级的步骤,比如处理数据库或软件产品的其他方面。
有许多种设置软件项目目录结构的策略,它们各有优缺点。我喜欢把源代码 与测试 分隔开的方式,而且我总是为它们创建不同的目录结构。处理第三方库的常用方式是创建一个 lib 目录。但是,对于处理第三方依赖项,还有可管理性更好的机制,比如 Ivy。
无论怎样设置项目的目录结构,目录结构越详细、组织性越强,效果就越好。随着项目资产(比如源代码文件和测试)数量的增加,目录结构的好处会越来越明显。图 1 给出一个简单的项目目录结构,其中包含一个用于第三方库的目录,以及两个用于源代码文件和相关测试的目录:
图 1. 一个简单的项目目录结构
![一个简单的项目目录结构](https://i-blog.csdnimg.cn/blog_migrate/b17862fa2445c957bbd9440a67a3f091.png)
注意项目根目录中的 build.xml 文件(见图 1)。这是项目的构建文件,它定义了一组最基本的自动操作,可以将源代码转移到可投入生产环境的状态。
CI 基础:代码编译
现在,可以开始编写代码了!本节为软件项目设置基础结构,包括设置项目类路径和编译。如果不执行这些预备步骤,后面的工作就不会有效果。
创建可靠且可重复的构建的第一步是限制硬编码的值,尤其是与文件系统路径相关的值,比如目录。因此,清单 1 定义了许多属性,以后可以在各种相关目标中引用它们:
清单 1. 在 Ant 中设置属性
<property name="default.target.dir" value="target" /> <property name="classes.dir" value="${default.target.dir}/classes" /> <property name="test.classes.dir" value="${default.target.dir}/test-classes" /> <property name="test.report.dir" value="${default.target.dir}/test-reports" /> <property name="lib.dir" value="${basedir}/lib" /> <property name="source.dir" value="src/java" /> <property name="test.source.dir" value="test/java" /> <property name="test.pattern" value="**/**Test.java" /> |
因为所有第三方库都放在 lib 目录中,所以可以通过扫描这个目录快速创建类路径,见清单 2。(注意,通过清单 1 中的 lib.dir
变量引用这个目录。)
清单 2. 根据 lib 目录中的 JAR 文件创建类路径
<target name="init"> <mkdir dir="${classes.dir}" /> <mkdir dir="${test.classes.dir}" /> <path id="build.classpath"> <fileset dir="${lib.dir}"> <include name="**/*.jar" /> </fileset> </path> </target> |
定义了类路径之后,就可以创建一个编译源代码的目标,见清单 3。
清单 3. 使用 Ant 的 javac 任务编译源代码
<target name="compile-source" depends="init" description="compiles all .java files in source directory "> <javac destdir="${classes.dir}" srcdir="${source.dir}" classpathref="build.classpath" /> </target> |
很容易通过一个称为 javac
的任务定义编译。这个任务使用类路径编译一个目录中的代码,并将类文件放在另一个目录中。
测试、监视和存档
如果想让 CI 提供真正的价值,除了持续编译之外,还需要做更多的工作。如果想提高代码质量,首先就要执行测试。可以使用 JUnit 或 TestNG。具体选用哪种测试框架并不重要,重要的是要经常 运行这些测试,也就是每当修改代码时都运行测试。
幸运的是,用 Ant 运行 JUnit 测试非常容易:Ant 允许通过 junit
任务运行 JUnit 测试。在配置 junit
任务时,需要考虑寻找 JUnit 测试的位置以及如何获取这些测试的结果。
清单 4. 通过 Ant 运行 JUnit 测试
<target name="test" depends="compile-tests" description="runs JUnit tests"> <mkdir dir="${test.report.dir}" /> <junit dir="${basedir}" printSummary="on" fork="true" haltonfailure="true"> <sysproperty key="basedir" value="${basedir}" /> <formatter type="xml" /> <classpath> <path refid="build.classpath" /> <pathelement path="${test.classes.dir}" /> <pathelement path="${classes.dir}" /> </classpath> <batchtest todir="${test.report.dir}"> <fileset dir="${test.source.dir}"> <include name="${test.pattern}" /> </fileset> </batchtest> </junit> </target> |
在清单 4 中,junit
任务运行在 test.source.dir(在 清单 1 中定义)目录中找到的所有测试。XML 报告写到另一个目录(test.report.dir)中。在开始配置 CI 服务器时,要使用这些测试报告写到的位置。
有了 Ant 这样的自动化机制,就可以以多种方法监视软件质量。有许多种扫描源代码和二进制文件的工具,但是其中最流行的两种是 PMD 和 FindBugs。PMD 扫描源代码文件,并根据一系列规则检查其中的代码。如果代码有问题,它就会报告一个违规。FindBugs 的作用与 PMD 相似,但是它扫描二进制文件(即类文件)并报告 bug。这两个工具都能够很好地与 Hudson 集成。
通过 Ant 运行 PMD 很容易:只需 下载它并按照说明操作!与运行 JUnit 测试一样,一定要指定在运行 PMD 时生成 XML 报告。
清单 5. 用 PMD 寻找代码违规
<target name="pmd" depends="init"> <taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpathref="build.classpath" /> <pmd> <ruleset>rulesets/basic.xml</ruleset> <ruleset>rulesets/braces.xml</ruleset> <ruleset>rulesets/javabeans.xml</ruleset> <ruleset>rulesets/unusedcode.xml</ruleset> <ruleset>rulesets/strings.xml</ruleset> <ruleset>rulesets/design.xml</ruleset> <ruleset>rulesets/coupling.xml</ruleset> <ruleset>rulesets/codesize.xml</ruleset> <ruleset>rulesets/imports.xml</ruleset> <ruleset>rulesets/naming.xml</ruleset> <formatter type="xml" toFile="${default.target.dir}/pmd_report.xml" /> <fileset dir="${source.dir}"> <include name="**/*.java" /> </fileset> </pmd> </target> |
PMD 有许多规则,可以对自己的代码库运行这些规则。清单 5 给出许多选项,但是选择运行哪些规则完全取决于您。
FindBugs 扫描二进制文件,而且只扫描包含项目文件的 JAR 文件常常更容易。清单 6 中的 findbugs
目标依赖于 jar
目标:
清单 6. 用 FindBugs 寻找 bug
<target name="findbugs" depends="jar"> <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" classpathref="build.classpath" /> <findbugs classpathref="build.classpath" pluginlist="${lib.dir}/coreplugin-1.0.jar" output="xml" outputFile="${default.target.dir}/findbugs.xml"> <sourcePath path="${source.dir}" /> <class location="${default.target.dir}/plainead.jar" /> </findbugs> </target> |
与 JUnit 和 PMD 任务一样,应该让 FindBugs 创建 XML 文件。
无论最终如何部署项目,都需要选择创建 WAR 或 JAR 文件。对于这个项目,我们创建一个 JAR 文件。如清单 7 所示,从一系列类文件创建 JAR 文件也很容易:
清单 7. 用 Ant 对二进制文件进行存档
<target name="jar" depends="test"> <jar jarfile="${default.target.dir}/plainead.jar" basedir="${classes.dir}" /> </target> |
在本节和前一节中,我们只创建了几个目标,而最终结果是一个可重复且可靠的构建系统,它几乎适合任何代码库。这个构建文件不但执行编译,而且还运行测试。它还使用两种检查工具评估代码质量。最后,这个构建文件将代码库打包成 JAR 文件。请记住,最后一步因项目而异。如果要构建 Web 应用程序,那么可能需要比清单 7 更多的控制;例如,可能需要用应用程序的文件创建一个 WAR 文件,然后把它部署到 Web 容器中。
没有 SCM,就没有 CI
有了可靠的构建过程之后,CI 过程的下一个需求是一个 SCM 存储库。市场上有许多 SCM 存储库,包括开放源码软件和商业产品。这些产品的基本用途都是管理源代码。SCM 系统可以进行源代码版本和历史管理,当多个开发人员在同一个文件上工作时,这两个功能十分重要。如果当前的软件项目没有使用 SCM 存储库,那么您可能应该停止阅读本教程,尽快设置并运行一个 SCM 系统。
Hudson 默认支持 CVS 和 Subversion,这两个软件都是免费的。Hudson 还有用来集成 Rational ClearCase(这是一个商业产品)的插件。
对于本教程,假设您使用 Subversion。如果不使用 Subversion,也不必担心:无论使用哪种 SCM 系统,基本原理都是一样的。实际上,在高层面上,CI 过程与 SCM 相关的方面是非常简单的。CI 服务器(在本教程中是 Hudson)向 SCM 查询对特定项目的修改。如果探测到修改,CI 服务器就执行更新(也就是获取 SCM 中的最新副本)并运行构建。在这个示例中,Hudson 运行本教程前面定义的构建。
按照 Subversion 的行话来说,项目驻留在存储库中。根据存储库的配置方式,可以通过一个 URL 访问项目,URL 实际上是存储库的路径加上项目名称。如果使用别的 SCM 系统,那么可能要使用其他机制访问存储库。无论是哪种情况,都需要正确地配置 CI 服务器来访问项目存储库。目前,假设您使用 Subversion,所以只需通过 URL 签出所需的项目。
假设项目名称是 solar-ci,存储库的 URL 是:
http://scm.acme.com/svn-repo/main/trunks/ |
所以,项目的访问 URL 是:
http://scm.acme.com/svn-repo/main/trunks/solar-ci |
必须根据 SCM 的设置方式正确地配置存储库访问;例如,Subversion 需要用户名和密码。在设置 CI 过程时,为 CI 服务器创建一个新用户常常是有意义的。对于这个项目,我们创建一个名为buildmaster
的新用户。在后面配置 Hudson 来监视项目时,为了执行签出和其他功能,需要显式地指定buildmaster
的凭证。
Hudson
CI 过程的最后一个方面是 CI 服务器本身。CI 服务器在整个开发过程中的主要作用是控制者:当服务器在代码存储库中探测到修改时,它将运行构建的任务委托给构建过程本身。如果构建失败了,那么 CI 服务器将通知相关方面,然后继续监视存储库。它的角色看起来是被动的;但是,它是快速反映问题的关键。
使用 Hudson 的主要好处之一是它的设置很简单。在最简单的情况下,Hudson 只需要两个步骤:
- 下载最新的版本(它打包为一个 WAR 文件)。
- 运行
java -jar hudson.war
。
这样就可以了。因为下载的是一个 WAR 文件,所以如果愿意,可以将它部署在 Tomcat 或 JBoss 等容器中。这完全由您自己决定。当然,Hudson 假设在安装它的机器上运行着 Java 5,而且如果定义了JAVA_HOME
环境变量,Hudson 就会使用它。(正如前面提到的,Hudson 需要 Java 5。)
在安装并运行 Hudson 之后(将 WAR 文件部署到 servlet 容器或从命令行执行 java -jar hudson.war
),启动浏览器并访问默认安装位置。如果通过命令行运行 Hudson 而且您在本地机器上,那么可以访问http://localhost:8080/
。
图 2. Hudson 已经就绪!
![Hudson 启动页面](https://i-blog.csdnimg.cn/blog_migrate/bb3f367f7faf330f3cf191b0b57bc4f0.png)
如果一切正常(实际上不太可能出问题),应该会看到图 2 所示的 Hudson 启动页面。
配置 Hudson 的第一步是让它知道在哪里可以找到构建平台的可执行文件。在这个示例中,用 Ant 作为构建系统,所以需要告诉 Hudson 本地机器上 Ant 的位置。如果使用 Maven,也必须做同样的工作:告诉 Hudson Maven 的位置。请记住,并不是告诉 Hudson 构建文件的位置,而是指定构建系统的可执行文件的位置,让它可以调用 构建文件。在这个示例中,需要指定 Ant 命令的位置,这让 Hudson 能够执行ant -f build.xml
命令。
如果访问 Hudson 主页的本地实例并单击左上角的 Manage Hudson 链接,应该会看到图 3 所示的可配置选项列表。
图 3. 配置 Hudson 非常容易
![Hudson 管理页面上显示的可配置选项](https://i-blog.csdnimg.cn/blog_migrate/9cb5ca437eada6ec9d964cd897d81f33.png)
在 Ant 部分中,需要提供安装 Ant 的路径,见图 4:
图 4. 向 Hudson 指出 Ant 的位置
![向 Hudson 提供 Ant 的路径](https://i-blog.csdnimg.cn/blog_migrate/759db5f11c9966c7cb37bdb9162dce40.png)
还可以配置服务器的其他几个方面,比如向 Hudson 提供一个电子邮件服务器的位置,以便在构建失败时接收电子邮件。根据您的组织设置电子邮件的方式,可能需要让系统管理员帮助设置这个特性。设置电子邮件并不是必需的;Hudson 还支持以 RSS 作为通知机制,对于某些人来说,这种方式比电子邮件更好。究竟选择哪些通知机制完全取决于您。(注意,这里说的是 “哪些”,也就是说,可以同时使用多种通知机制!)
最后,在配置项目之前,需要让 Hudson 能够与您的 SCM 系统通信。在这个示例中,需要设置 Subversion 存储库的路径和访问存储库所需的凭证。
在浏览器中访问 http://localhost:8080/scm/SubversionSCM/enterCredential
,并指定项目的存储库 URL 以及正确的凭证(比如 buildmaster 等等)。这个一次性的步骤确保 Hudson 可以正确地与 Subversion 通信。(当然,如果决定改用另一个 URL 上的另一个 Subversion 存储库,就必须重新设置。)
既然 Hudson 已经能够与 SCM 存储库通信了,就该配置项目了。这个示例所用的项目称为 solar-ci
。在 Hudson 主页上单击左上角的New Job 链接。这时会看到图 5 所示的屏幕:
图 5. 在 Hudson 中配置作业
![在 Hudson 中配置作业](https://i-blog.csdnimg.cn/blog_migrate/f8f8a8854096685319f15a78fb452e06.png)
给作业命名(对于这个示例是 Solar Project)并选择 Build a free-style software project 选项。如果使用 Maven 2,那么 Hudson 可以根据项目的配置文件快速配置项目。
单击 OK 之后,会进入项目配置屏幕,在这里可以指定以下内容:
- 要连接的 SCM
- 构建项目的频率
- 要调用的构建平台(Ant、Maven 等等)
另外,还可以配置一些构建后操作,比如发送电子邮件或发布相关软件资产。图 6 所示选项的意义很明确,不需要解释。在 Hudson 中设置 CI 项目就是这么容易。
图 6. 在 Hudson 中配置项目细节
![在 Hudson 中配置项目细节](https://i-blog.csdnimg.cn/blog_migrate/0b472d0d14ac900cd779b388cef29c9e.png)
对于这个项目,需要选择 Subversion 选项,然后至少需要指定项目的 URL。
在 Build Triggers 部分中有许多选项。我发现 Poll SCM 选项非常有用,它决定 Hudson 检查 SCM 的频率。这个设置取决于您的需要;如果开发团队很大,对代码的修改很频繁,就需要比较频繁地检查 SCM(比如每 5 分钟一次)。
所以,选择 Poll SCM 选项,然后在 Schedule 框中输入 * * * * *
,这让 Hudson 每分钟检查一次。这样设置有助于进行演示(在修改 SCM 中的代码之后,不用等很久就会触发构建过程),但是在对 CI 过程满意之后,要记住指定更合理的值。单击问号(?)图标,可以了解关于配置 cron tab 的更多信息。
在 Build 部分中,选择 Invoke Ant 选项,选择前面配置的 Ant 版本,然后指定构建文件中要执行的目标。目前,只需让 Hudson 执行测试目标。这个目标将编译所有源代码文件,然后运行已经定义的所有 JUnit 测试。
在 Post-Build Actions 部分中,选择 Publish JUnit test result report 选项。必须指定在通过 Ant 运行 JUnit 时生成 XML 文件的位置。如果 Subversion 中的项目名称是solar-ci
,而且构建文件把这些报告直接写到target/test-reports
目录中,那么应该输入solar-ci/target/test-reports/*.xml
,见图 7:
图 7. 在 Hudson 中配置 JUnit 趋势
![在 Hudson 中配置 JUnit 趋势](https://i-blog.csdnimg.cn/blog_migrate/2206de4b20cb6503303d707bc3d3e6c8.png)
单击 Save 保存配置。
如图 8 所示,Hudson 显示一个包含许多选项的项目主页,可以在这里修改配置、强制执行构建、查看与项目资产相关的修改等等!下一节讨论如何使用这些选项更深入地观察软件项目的所有方面。
图 8. Hudson 中的 CI 项目主页
![Hudson 中的 CI 项目主页](https://i-blog.csdnimg.cn/blog_migrate/24b4fa01a15679dd6e2d1e93ad514b23.png)
准备好执行 CI 了吗?
现在已经在 Hudson 中正确地配置了 CI 项目,所以差不多 可以开始运行它了!但是在此之前,我们来检查一下项目的设置是否正确。
我们已经将 Hudson 配置为每分钟检查一次,而这个项目还没有构建过,所以 Hudson 很快就会自动触发构建过程。检查项目的主页,在页面左下部分的 Build History 框中会看到一个新条目,见图 9:
图 9. Hudson 运行了一个构建!
![Hudson 运行了一个构建](https://i-blog.csdnimg.cn/blog_migrate/a4b5f64fe0ee64f15023861e70cf8399.png)
单击这个构建的日期,就会显示这个构建的详细信息,见图 10:
图 10. Hudson 的 Build Status 页面
![Hudson 的 Build Status 页面](https://i-blog.csdnimg.cn/blog_migrate/5fda0fe23c306418efbaf066ca7ada9a.png)
在图 10 所示的 Build Status 页面上,可以查看对源代码的修改情况(这个初始构建没有报告修改)以及测试结果。另外,还可以通过单击 Console Output 链接查看构建过程的输出。
如果返回项目的主页,就会注意到可以通过 RSS 订阅项目的 Build Statu 页面。还要注意,可以订阅所有构建的报告,也可以只订阅失败时的报告。我常常选择订阅failures,因为在构建失败时我需要得到警告。
除了检查构建状态之外,Hudson 还允许强制执行构建(单击 Build Now 链接)、查看对多个构建的修改以及查看 Hudson 执行构建的工作空间。(如果需要获取项目资产,例如 WAR 文件,可以在工作空间中进行操作。)
编写一些代码!
CI 过程需要三个组件:构建、SCM 和 CI 服务器。现在,这些都设置好了,可以开始执行 CI 了。当然,为了让 CI 发挥作用,需要编写一些代码并进行修改。在本节中,我们从头到尾看看 CI 过程是如何工作的。
在研究完整的 CI 场景之前,先谈谈使用 CI 时的开发节奏。使用 CI 服务器运行构建并不妨碍您亲自运行本地构建。实际上,在正确的 CI 过程就位之后,它会将 SCM 中的失败通知您,所以要尽可能确保新代码或修改的代码工作正常,然后才能将它们签入 SCM 存储库。这样做可以避免大量警告不必要地干扰所有开发人员。
因此,CI 环境中的开发节奏应该是下面这样:
- 对本地沙箱执行更新。
- 修改一些代码。
- 编写一些测试。
- 运行本地构建,从而确保您没有破坏任何东西。
- 执行更新,从而确保从 SCM 获得最新的代码;然后再次运行构建,确保一切正常。
- 签入您的修改。
注意,如果您和团队的其他成员同时在代码库中工作,那么第 5 步是非常重要的,尤其是在您的工作花费了很长时间的情况下。
大多数开发人员会发现 CI 的节奏是很自然的。无论是否在 CI 环境中,这都是良好的工作节奏!
现在,可以开始做一些真正的工作。例如,当 Hudson 运行示例项目的第一次构建时,它发现并运行两个 JUnit 测试;因此它报告这两个测试都通过了,它们都是新的测试,见图 11:
图 11. 这些测试顺利通过了
![Hudson 的测试报告](https://i-blog.csdnimg.cn/blog_migrate/85cbece1bd5a69eff1784daffb4b8efa.png)
您可能为这些测试顺利通过感到高兴,也可能不以为然,认为它们没有提供足够的覆盖范围。(当然,可以获取覆盖报告,但是目前不必这么做。)为了确保代码是完善的,编写另外两个测试,在本地运行它们,执行更新,再次运行构建,然后签入它们。在此之后,编写另外两个测试 并重新执行相同的步骤。
在签入两个新测试之后,可以访问项目的主页,检查它们的状态。过一会儿,在 Build History 框中会出现一个尚未完成的构建。Hudson 已经在 SCM 存储库中探测到了修改!
图 12. Hudson 对 Subversion 中的修改做出反应
![Hudson 对 Subversion 中的修改做出反应](https://i-blog.csdnimg.cn/blog_migrate/4d40f8a37f8c2a2523877f371dcffa7a.png)
在图 12 中,Hudson 的 Build History 框显示一个尚未完成的构建。当这个构建完成之后(应该会顺利地完成),可以单击这个构建的日期来查看详细信息,见图 13:
图 13. 运行两个新测试之后的详细信息
![两个新测试的详细信息](https://i-blog.csdnimg.cn/blog_migrate/de71628daa92ccc70460ad790ff62a95.png)
图 13 显示触发这个构建的修改。可以单击每个修改旁边的细节链接来查看关于修改的更多信息,比如谁做了这个修改和它们的注释语句,以及图 14 所示的特定细节:
图 14. 在这个构建中发现两处修订
![在这个构建中发现两处修订](https://i-blog.csdnimg.cn/blog_migrate/50c147d0b3de1cddf99584a5a3fafa7f.png)
如果返回构建状态页面并单击 Test Result 链接,那么可以看到运行了两个新测试,所有四个测试都通过了,见图 15:
图 15. 测试报告
![Hudson 的测试报告](https://i-blog.csdnimg.cn/blog_migrate/a6cd0adc3a8e4872b2b5135d488b47db.png)
在配置这个项目时,我们指定了 JUnit 的结果文件,但是我没有说明为什么这么做。这么做是为了演示 Hudson 支持的一个出色的开箱即用特性。对于每个构建,Hudson 会解析对应的 JUnit 结果 XML 文件并生成趋势图。实际上,如果返回项目的主页,就会看到一个趋势图,它显示对于到目前为止的两个构建,测试数量已经加倍。
图 16. 一个不错的趋势,测试数量已经加倍
![趋势上升](https://i-blog.csdnimg.cn/blog_migrate/7dd30671abb20bac96c576b42b02c384.png)
图 16 中的趋势图表明,在 Builds 6 和 8(在这里,它们是连续的构建)之间测试数量已经加倍。
如果再编写两个测试,那么图 16 中的趋势图会继续显示非常不错的趋势。也就是说,随着每个构建的运行,测试的数量逐渐增加,见图 17。还可以看出这些测试一直顺利通过,因为趋势图是蓝色的,而不是红色:
图 17. 测试趋势持续上升!
![测试趋势持续上升!](https://i-blog.csdnimg.cn/blog_migrate/ef742f4bfc3ba1f0cba231fe1ec1bfe0.png)
Hudson 不但生成测试的趋势,还生成构建执行时间的趋势,所以很容易观察构建的性能。例如,如果构建的执行时间开始变长,那么可能要考虑以不同的方式运行测试(比如对测试进行分类),从而加快构建过程。
图 18. 这些测试要花时间运行!
![检查构建执行时间](https://i-blog.csdnimg.cn/blog_migrate/6cc3eafc74c105e69c690eefce9bc642.png)
图 18 所示的数据清楚地表明,测试增加了构建的总执行时间;编译这些测试要花费时间,但是与运行它们的时间相比,编译时间非常短。
如果返回 Hudson 的主页,就会看到 Hudson 当前管理的项目列表。在项目表中可以找到各种数据。其中比较有意思的是 W 列中的第二个图标:它表示监视的项目的总体健康程度。如图 19 所示,这个图标目前是发光的太阳,这表示对于所有构建,测试都通过了:
图 19. 阳光明媚的好日子
![太阳图标表示测试通过了](https://i-blog.csdnimg.cn/blog_migrate/415d9f829c90e00df3d132840f698fd7.png)
与真实生活中一样,不可能每天都是阳光明媚的;当出现问题时,例如项目无法构建或测试失败了,Hudson 会显示另一个图标,见图 20:
图 20. 乌云密布
![乌云表示测试失败](https://i-blog.csdnimg.cn/blog_migrate/0827a8fd622f77848a1e0ebf1bf2a034.png)
如果仔细观察,就会看到一个弹出框,它指出某个项目的构建稳定性是 79%,测试稳定性是 97%,这相当于多云的天气。但是,最上面的项目非常糟糕。
Hudson 的插件
在这个项目开始时定义的构建文件运行软件检查器 FindBugs 和 PMD,分析代码并报告违规。将这些任务添加到 CI 过程中,就可以很好地监视代码库。常常运行它们,就可以观察结果的趋势,就像前面看到的测试和构建时间趋势一样。
Hudson 比较令人感兴趣的特性之一是它的插件 API,这个 API 有助于创建新特性并根据需要安装。Hudson 支持许多插件,包括用于生成 FindBugs 数据趋势的插件和生成 PMD 违规趋势的插件(这个插件也可以为其他工具生成趋势,包括 CheckStyle)。
就像安装 Hudson 本身一样,安装 Hudson 插件也是很容易的。只需要从 Hudson 的 Web 站点下载插件的最新版本,单击 Hudson 主页上的Manage Hudson 链接,然后单击 Manage Plugins 链接,这时会显示一个用来上传插件存档文件的表单。上传插件之后,必须重新启动 Hudson。
下载并安装 FindBugs Hudson 插件之后,就需要配置它。插件的配置在项目级进行,所以要访问项目的主页并单击 Configure 链接,然后会看到与 FindBugs 相关的一些新选项,见图 22:
图 22. 在 Hudson 中配置 FindBugs
![在 Hudson 中配置 FindBugs](https://i-blog.csdnimg.cn/blog_migrate/d439198a74a0ccee7a2f3ac23c27619b.png)
配置 FindBugs 插件的步骤与配置 JUnit 趋势很相似。必须指定 FindBugs 输出 XML 报告的位置(在定义构建文件中的 FindBugs 目标时使用过这个设置)。除了报告的位置之外,还可以指定一个阈值。如图 22 所示,指定阈值为 5 表示如果有 5 个以上的 FindBugs 违规,就认为这个构建是不可靠的。还可以用阈值影响构建报告。
单击 Save 之后,需要更新项目的构建首选项,这意味着需要确保 Hudson 在构建过程中运行 FindBugs。在此之后,需要强制执行构建(否则只能等待有人签入修改),从而开始收集 FindBugs 数据。
图 23. 发现两个警告
![发现两个警告](https://i-blog.csdnimg.cn/blog_migrate/63ec93d1a37a61f02f1abf934c249cf5.png)
执行第一个构建之后,在构建状态页面上会出现一些新的数据点,见图 23。在这个示例中,Hudson 报告在运行 FindBugs 时发现了两个警告。通过单击 Hudson 报告页面上的FindBugs 链接,可以查看更多的信息。
图 24. FindBugs 违规细节
![Hudson 显示 FindBugs 发现的代码违规](https://i-blog.csdnimg.cn/blog_migrate/eb5adb79db31c64c6d8d1df115f5eaa6.png)
查看细节有助于纠正问题,见图 24。在这个示例中,可以看到项目的两个类出现了违规。一个是 Priority 1 违规,另一个是 Priority 2 违规。根据 CI 的开发节奏,应该马上开始纠正这些问题。
纠正错误并将修改签入 Subversion 之后,Hudson 最终会执行构建。在执行构建之后单击 Build Status 链接,会看到 Hudson 指出一个 bug 已经纠正了,见图 25:
图 25. 一个 bug 被纠正了,另一个仍然存在
![Hudson 显示一个 bug 被纠正了,另一个仍然存在](https://i-blog.csdnimg.cn/blog_migrate/d088cbe63a077559ec93c86957d9a07a.png)
另外,项目主页上会显示新的趋势图(见图 26),它表明已经纠正了一个 bug:
图 26. bug 数量呈下降趋势 —— 不错!
![趋势图显示 bug 数量呈下降趋势](https://i-blog.csdnimg.cn/blog_migrate/eba54837a4247612052307f067778233.png)
显示 FindBugs 违规的趋势和使用 bug 阈值影响构建的总稳定性是两个出色的 Hudson 特性。而且,设置这两个特性非常容易。只需下载并安装 FindBugs,就可以在项目构建过程中运行 FindBugs。
还有一些 Hudson 插件与代码检查相关,其中最著名的是 Violations 插件,它可以为多种工具生成趋势,包括 PMD、CheckStyle 和 FindBugs 等。既然已经运行了 FindBugs 插件,就可以用 Violations 插件专门监视 PMD 数据。在本教程前面设置 Ant 构建文件时,我们让 PMD 输出 XML 报告。Violations 插件使用这些报告分析数据趋势。
如图 27 所示,这个插件的设置方式与 Hudson 中的其他趋势工具相似:只需指定要监视的工具的 XML 报告位置。当然还有其他选项,但是对于基本特性,指定 XML 报告的位置并单击Save 就够了。
图 27. 配置 Violations 插件
![配置 Violations 插件](https://i-blog.csdnimg.cn/blog_migrate/e90a1204b9019f50ff5b9a71eec9894a.png)
请记住,需要让 Hudson 实际调用运行这些工具的 Ant 目标;在这个示例中,已经定义了一个 PMD 目标,所以只需更新 Hudson 来运行 PMD(以及 FindBugs 和测试,运行测试会导致编译)。
在执行构建并研究 PMD 数据之后,可以开始纠正代码。如图 28 所示,在 Builds 13 和 14 之间减少了 13 个 PMD 违规。不错吧?代码已经越来越好了!
图 28. 已经纠正了一些 PMD 违规!
![纠正了 PMD 违规](https://i-blog.csdnimg.cn/blog_migrate/48fce7194ad52cc236a3ee4582808335.png)
Violations 插件也支持趋势;如图 29 所示,它甚至按照工具分别生成数据趋势;在这个示例中,它同时寻找 FindBugs 数据和 PMD 数据。随便问一句,您能够从图 29 中看出一些 FindBugs 违规也已经被纠正了吗?
图 29. 违规呈下降趋势
![违规呈下降趋势](https://i-blog.csdnimg.cn/blog_migrate/3e71604070af6cf062e8f8cccc90418b.png)
与前面看到的测试趋势和 FindBugs 趋势一样,Violations 插件也在项目的主页上显示总违规数量的变动情况,见图 30:
图 30. 违规数量随时间变化的趋势
![总违规数量随时间变化的趋势](https://i-blog.csdnimg.cn/blog_migrate/5e4708853490029035e71477e96de8f0.png)
显然,Hudson 的插件让我们能够一目了然地看到代码基的情况;而且,显示数据趋势的特性让我们能够设置目标并监视它们的变化,这样就不必反复查看定制报告的具体内容。从某种意义上说,Hudson 创建了一个项目 “仪表板”,让我们能够快速理解数据。
结束语
持续集成过程要经常编译、测试、检查和部署源代码。CI 的好处很容易理解:经常组装软件,就可以在早期发现问题,而问题在早期还不复杂,容易解决。
在本教程中,您了解了 CI 的基本方面。我介绍了一些用于 CI 的工具(即 Hudson、Ant 和 Subversion),并讲解了如何使用这些工具设置 CI 环境。本教程使用的框架提供一个可重复且可靠的构建过程,这个构建过程运行 JUnit 测试并通过 PMD 和 FindBugs 进行软件检查。学习了如何正确地配置 Hudson CI 服务器,让它检查 SCM 存储库并在探测到修改时运行构建过程。还学习了如何使用 Hudson 的插件生成各种结果的趋势。
现在您应该有信心快速地构建高质量的软件了!
学习
- “ 让开发自动化:持续检查”(Paul Duvall,developerWorks,2006 年 8 月):了解 CheckStyle、JavaNCSS 和 CPD 等自动检查器如何增强开发过程以及什么时候应该使用它们。
- “ 让开发自动化:持续反馈”(Paul Duvall,developerWorks,2006 年 11 月):讨论可以合并到 CI 系统中的各种反馈机制。
- “自动进行团队构建和单元测试过程”(Mark Wilkinson,developerWorks,2005 年 10 月):讨论如何解决在为 Java 项目设置基于 Linux 的构建服务器时涉及的实际问题。
- “Don’t belie CI”(Andrew Glover,thediscoblog.com):持续集成不仅仅是使用 CI 服务器,实际上构建过程是更重要的方面。
- “Don't focus on the elephant”(Andrew Glover,thediscoblog.com):对比手工代码检查和软件检查。
- “The Future of Continuous Integration”(Paul Duvall,testearly.com):CI 不够主动,因为代码已经提交了;那么,如何在第一时间防止有缺陷的代码进入 SCM 系统呢?
- 追求代码质量 (Andrew Glover,developerWorks):阅读 Andrew 撰写的代码质量系列中的所有文章,其中涉及代码度量、测试框架和重构等主题。
获得产品和技术
- Hudson:自己体会一下使用这个 CI 服务器是多么容易。
- JUnit:用这个标准的 Java 平台测试框架编写有效的开发人员测试。
- FindBugs:通过用 FindBugs 扫描字节码,快速寻找缺陷。
- PMD:应用 PMD 的数百条规则测试代码并捕捉错误。