5.4 执行基于 Jenkins 的 CI
已经集齐了所需要的版本管理系统、自动 build 以及测试代码之后,接下来就让我们实际使用 Jenkins 来实施 CI 吧。
5.4.1 Jenkins 的安装
Jenkins 的安装非常简单。从主页 36 下载 WAR 文件并执行以下命令即可。
$ java -jar jenkins.war启动 Jenkins 后访问 http://localhost:8080 ,若显示如图 5.7 这样的画面,即说明安装成功了。
图 5.7 Jenkins 安装完成画面
●…… 使用本地安装包进行安装
在刚才的例子中,为了体现启动 Jenkins 有多么简单,选择了直接使用 WAR 包启动。而在实际的开发现场,则是将 Jenkins 作为 OS 的服务(UNIX/Linux 环境的守护程序)启动比较好。Jenkins 为各个 OS 提供了安装包。例如,使用 Windows 平台的安装包进行安装就能作为 Windows 的服务启动,使用 CentOS 的安装包就能作为 Linux 的驻守程序启动。Jenkins 的主页上记载有各个 OS 的本地安装包的安装方法,可以参考。
这里以 Red Hat Enterprise Linux 为例进行讲解。新建 Jenkins 用的目录并执行 yum 命令即可。启动脚本也会一并安装。
$ wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo $ rpm —import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key $ yum install jenkins执行如下命令,即可将 Jenkins 作为 OS 的守护程序启动。
$ /etc/rc.d/init.d/jenkins start和使用 WAR 启动时一样,启动后请试着访问 http://localhost:8080 ,若显示如图 5.7 这样的画面,即说明安装成功。
顺便提一下,Jenkins 还能在 Tomcat37 等 Servlet 容器上启动。这时会监听 ajp 的 8009 端口,所以用 proxy_ajp 等作为代理上传至服务器即可。
5.4.2 Jenkins 能干些什么
Jenkins 是一款为实施 CI 提供支持的工具,通过和各类插件组合使用,可以实现非常丰富的功能。详细内容可以参考 Jenkins 相关的书籍 38 。
38 John Ferguson Smart 著,Jenkins: The Definitive Guide,O'Reilly Media, Inc, USA,2011
这里对 Jenkins 所支持的最基本的 CI 进行说明。先前列举了 CI 所必需的版本管理系统、build 工具、测试代码,将这些组合起来构建如下这样的机制。
下载(checkout)代码
自动 build 并执行测试
统计结果并制作报表
通知
让我们试着来创建一种机制,实现当代码有更新时能够立即并且持续地执行上述一系列处理。
5.4.3 新建任务
Jenkins 以任务(Job)为单位来管理一系列的处理流程。可以姑且理解为 1 个应用对应 1 个任务,或者 1 个目录对应 1 个任务 39 。
39 实际上 1 个任务也可以操作多个目录,通过配置可以实现各种不同的运用。
首先在 Jenkins 上新建任务。点击菜单上的“创建一个新任务”,会出现“构建一个自由风格的软件项目”“构建一个 Maven2/3 项目”“构建一个多配置项目”“监控一个外部的任务”“复制已有的 Item”5 个选项,这些选项各自的内容在 Jenkins 的主页上都有说明,请自行参考。
如果使用 Maven bulid 工程的话,可以选择“构建一个 Maven2/3 项目”。Jenkins 支持向 Maven 目录公开(publish)所生成的文件之前的流程。而如果是 Ruby、Node.js 等 Java 之外的项目,或者是使用 Maven 以外的 Java 项目,请选择“构建一个自由风格的软件项目”。
5.4.4 下载代码
下面进行从版本管理系统下载(checkout)代码的配置。虽然这里写的是“代码”,但是应该进行版本管理的并不仅限于代码,还包括数据库模式的定义和配置文件等应用程序启动所需的所有资源。用 Jenkins 将这些资源下载到干净的环境。
上述内容在代码管理这一项目中进行配置。Jenkins 默认可选的代码管理系统为 CVS 和 Subversion,使用其他的版本管理系统需要安装插件。
这里以使用 Git 作为前提进行讲解。点击菜单中的“系统管理”,打开“管理插件”,在“可选插件”标签下查找名为“Git Plugin”的项目(图 5.8)。
图 5.8 Git Plugin
点击“下载待重启后安装”按钮,重启 Jenkins,完成安装。再次打开任务的配置页面,确认一下代码管理的地方,可以看到增加了名为 Git 的选项。按照图 5.9 进行配置。
图 5.9 Git 的配置
图 5.9 中,在“Repository URL”中填写 Git 的 URL,在“Branches to build”中填写 build 对象的分支名称。然后在“源码浏览器”处填写阅览 Git 时使用的工具,这里选择了 githubweb,其他还有 Gitlab 和 Gitrious 等选项。
5.4.5 自动执行 build 和测试
下面对 build 进行配置。分别对“构建触发器”和“构建(build)”进行设置。
在“构建触发器”中配置执行 build 的触发器。
●…… 定期执行
比如 1 天执行 1 次、每隔 4 小时执行 1 次等,设置为定期自动执行 build 的方式。可以用近似于 cron 的写法来设置执行的频率。
定期执行是从还没有 CI 这个词汇时就有的手法。例如 Joel on Software 40 中写到了 Microsoft 从 1990 年代开始 1 天进行 1 次 build(daily build)。还有描写 1990 年代初 Windows NT 的开发的《观止——微软创建 NT 和未来的夺命狂奔》41 ,其中也提到了 daily build。
40 Avram Joel Spolsky,Joel on Software ,APress,2004 年
41 《观止——微软创建 NT 和未来的夺命狂奔》G. Pascal Zachary 著,张银奎译,机械工业出版社,2009
daily build 严格来说并不是 CI,但配置简单,实施起来也算是不错的方式。1 天之内提交的量并不是太多的话,进行 daily build 也不会有什么问题。也有一些案例是因为 build 或测试的执行时间过长,导致无法及时 build 而不得不采用 daily build 的方法。定期执行作为 CI 来说虽然有所欠缺,但比起什么都不做来说还是要好很多的。
●…… 轮询版本管理系统
即配置为以轮询(polling)的方式监视版本管理系统,有新的提交(Push)时就执行 build 的方式。能够用类似于 cron 的写法来指定轮询的频率。由于在每次提交时都会进行 build 及测试,因此能够更早地发现问题,有助于质量的提高。
因为采取的是轮询的方式,所以无论是否有提交都会向版本管理系统发送请求。虽然这会给系统造成不必要的负担,但只要没有太大的问题,采用轮询的方式还是能够比较方便地实施 CI 的。
build 执行的频率增加后,build 的速度就变得很重要。过于缓慢的话,提交集中时,build 会积累在任务队列中,变得难以及时地察觉问题。为了避免这样的情况,就需要在 build 或测试代码上下功夫,提高 CI 服务器的处理速度,增加服务器台数实行并行 build 等。
尽可能地避免定期执行,以每次提交时进行 build 这样的循环来实施 CI 是很重要的,因为这样版本管理系统的提交记录和 CI 服务器的 build 结果就能关联起来,工程的可追溯性能够得到提高。
专栏 从版本管理系统进行 Push
从 Jenkins 的配置来说,“轮询版本管理系统”是最适合于 CI 的,但最好能够由版本管理系统通过 Push 来请求 Jenkins 执行 build。Git 和 Subversion 都能够配置 Push,这里介绍下使用 Git 的 方法。
Git 提供了在发生提交或收到 Push 等事件时执行特定脚本这样的钩子(hook)的机制。在各个代码库下的 .git/hooks/ 目录下有示例文件,编写自己的脚本时可以进行参考。详细内容请见 Pro Git 42 。 Subversion 也有钩子的机制,可以实现相同的功能。
例如,在代码被 Push 到 Git 代码库时,若要执行 Jenkins 的任务,可以创建 .git/hooks/post-receive 文件,并按照下面的内容进行编辑。上述处理是以安装 Jenkins 的 Git Plugin 为前提的。
#!/bin/sh
curl http://{Jenkins服务器的域名或IP地址等}/jenkins/
git/notifyCommit?url={Git代码库的URL}&branches={分支名 (可以设置多个)}
curl 部分也可以是 wget 等命令,只要配置后能够向 Jenkins 发送上述这样的 GET 请求即可。
通过上述配置,在 Git 代码库收到 Push 时就会向 Jenkins 发送上述 GET 请求。收到请求的 Jenkins 会执行包含对应代码库的所有任务。关于这个功能的详细内容请参考 Git Plugin 的帮助 43 。
如果使用的是 GitHub 的话,可以利用 GitHub 的 Service Hooks 设置。打开代码库 Settings 下的 Service Hooks 就能看到,GitHub 能够和全世界的各类服务进行交互(图 5.a)。
图 5.a Service Hooks
从中选择 Jenkins,并配置好 Jenkins 的 URL,就能同样在 GitHub 收到 Push 时向 Jenkins 发送执行任务的请求。但是需要注意的是要将 Jenkins 搭建在 GitHub 可见之处。通常 Jenkins 多被搭建在公司内部的局域网(LAN)中,所以需要在网络的配置上下一些功夫。顺便提一下,GitHub 发送请求的服务器 IP 地址是公开的,只要对 Jenkins 加上 IP 限制,就能够消除安全方面的隐患。
这样的方法能够消除延迟,实现在每次提交后立即实施 build。
42 http://git.oschina.net/progit/
43 https://wiki.jenkins-ci.org/display/JENKINS/Git+plugin
●…… build 的记述
下面来讲一下 build 以及测试的处理。可以在“增加构建步骤”中选择 Ant、Maven、shell 脚本、Windows 批处理文件中的任意一项。Java 的项目可以选择 Ant 或 Maven。
如果是 Java 以外的语言的话,可以用 shell 脚本来记述 build 的处理。使用 Make、Rake 或 SBT 等 build 工具的话也没有问 题。虽然在 Jenkins 的配置页面上能够直接编辑 shell 脚本,但还是建议将 build 脚本记录在文件中并提交到版本管理系统,在 Jenkins 中只做简单的调用。这样即便没有 Jenkins,也能够以完全相同的方法来执行 build,非常方便。
另外,如果不使用 build 工具,而是利用 shell 脚本来描述 build 的话,请设置相适应的退出值(exit code)44 。Jenkins 根据该值来判断任务的执行是失败还是成功。
44 成功的话返回“exit 0”,失败的话返回“exit 9”。
5.4.6 统计结果并生成报表
可以在“构建后操作”中选择结果的通知方式。
可选的项目各种各样,这里为了便于统计测试结果,请选择“Publish JUnit test result report”。随后就会出现设置测试结果 XML 文件的路径的输入框,输入测试结果 XML 文件的路径即可。这样配置后再进行 build,就能如图 5.10 这样在浏览器上确认结果。
图 5.10 测试结果
从图 5.10 中可以看出,253 个测试中有 5 个测试失败了。测试所消耗的时间也能从图上看出。从“时期”这一列可以知道这是从在此之前的第几次 build 开始出现失败的,图 5.10 中的 5 个测试都是在这次 build 中才失败的。其他还有一些针对包的测试,可以看出和上一次 build 相比较,失败的个数有所增加。
专栏 以 JUnitXML 的形式输出报表比较高效
通过使用插件,还可以对 JUnitXML 之外的测试结果进行统计。但考虑到通用性和报表的直观性,以 JUnitXML 形式输出的报表是最有效的。如果使用的测试框架是 JUnit 的话自然没有问题。如果使用的是其他测试框架,因为报表生成部分的实现大多能以插件的形式进行替换,所以这里推荐修改为以 JUnitXML 的形式来生成报表。
例如 Scala 的 Specs2 就能够通过修改配置来改变生成报表的形式。JavaScript 的 Jasmine 也有志愿者制作的名为 JUnitXmlReporter 的输出 JUnitXML 的对象 45 。其他的语言也基本都能够输出 JUnitXML,因此建议先调查清楚再着手应对。
45 https://github.com/larrymyers/jasmine-reporters
5.4.7 统计覆盖率
至此,我们实现了到统计持续 build 及测试结果为止的处理流程。通过自动执行测试,对于测试覆盖的部分就能够确认测试是否成功,但测试没有覆盖的部分就没有任何信息。这样的情况下,让我们来统计一下代码覆盖率(code coverage)。代码覆盖率是表示测试对象的应用程序代码在测试中被执行了多少的指标。统计覆盖率能够看出代码被测试的程度,或者反过来说,能够使还需要增加多少测试这样的信息可视化。
但需要注意的是,代码覆盖率只是反映了测试对象的应用程序代码是否被执行,并不能反映出用作测试的测试代码是否妥当,因此并不可以盲目相信。但在认识到需要通过代码审阅(source review)等方式来确保测试代码的内容的正确性之后,代码覆盖率作为质量指标之一会变得很重要,所以请一定要试着用一下。
●…… 覆盖率统计工具
统计覆盖率需要安装专门的工具。具有代表性的覆盖率统计工具有下面这些。
Cobertura 46
Jacoco 47
Scct 48
SimpleCov 49
Rcov 50
46 http://cobertura.github.io/cobertura/
47 http://www.eclemma.org/jacoco/
48 https://github.com/sqality/scct
49 https://www.ruby-toolbox.com/projects/simplecov
50 https://github.com/relevance/rcov
这些工具是以 Ant、Maven、SBT、Rake 等 build 工具的插件的形式提供的。可以根据开发环境选择合适的工具。这次以作为 Maven 的插件提供的 Cobertura 为例进行讲解 51 。
51 Cobertura 已经于 2011 停止了开发,所以今后改用 Jacoco 比较好。因为习惯了用 Cobertura 编写报表的方式,笔者个人比较喜欢使用 Cobertura,而且现在还在使用。Java 之外的语言的覆盖率统计工具也大都支持 Cobertura 形式的 XML。
●…… Maven Cobertura 插件的安装
在 Maven 中使用 Cobertura,只需要在 pom.xml 中定义如下依赖关系即可。
<dependencies> <dependency> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.5.2</version> </dependency> </dependencies>依赖关系定义完后,执行下列命令。
$ mvn cobertura:cobertura这样就会执行测试,并在测试之后对覆盖率进行统计。结果文件以 html 的形式生成在 target/site/cobertura/ 目录下。可以在浏览器上打开 index.html 来确认报表(图 5.11)。
图 5.11 覆盖率的报表
图 5.11 中因为还没有编写测试,所以覆盖率为 0%。添加测试后就能通过报表确认覆盖率上升。作为供人阅览的报表来说,这样的 HTML 形式确实看起来比较方便,但就利用 Jenkins 来实现 CI 来看,还是以计算机易于理解的 XML 形式输出文件比较方便。Cobertura 的报表的默认形式是 HTML,可以通过修改配置同时生成 XML 形式的报表。
如下这样编辑 pom.xml。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 略 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <configuration> <formats> <format>xml</format> <format>html</format> </formats> </configuration> </plugin> </plugins> </build> <dependencies> 略 </dependencies> </project>在这样的状态下再次执行 mvn cobertura :cobertura ,就会发现除了之前的 html 文件之外,还生成了名为 coverage.xml 的文件。
这样覆盖率的统计就完成了。接着让我们对 Jenkins 进行配置。
专栏 Java 程序库的查找方法
笔者参与过数个开发现场,其间所体会到的事情之一就是使用 Java 的开发现场中程序库的查找方式以及安装方法存在不恰当的地方。
在 Web 上检索相关信息,下载可能符合条件的 jar 文件,并将其加入 class path 使用,这样的事例意外的多。如此一来程序库的可信度就有问题了。
Perl 和 Ruby 这样的脚本语言,各个社区之间彻底地应用了包(package)管理。但 Java 不全是这样。脚本语言从早期就采取了包管理的机制,和 OSS 亲和性比较高。
Java 也有包管理机制,如 Maven 仓库。访问 http://search.maven.org 就可以看到很多 Java 系语言(也包括 Scala、Groovy、JRuby 等)的 OSS 程序库。
例如,搜索 Cobertura,可以找到几个符合条件的程序库。这里试着使用一下 Cobertura 的 Maven 插件(图 5.b)。
图 5.b Cobertura 的 Maven 插件
从图 5.b 可以看出如何在 Maven 的 pom.xml 中添加依赖关系来安装 Cobertura。图中还记载了使用 Ivy 和 SBT 以及其他 build 工具时的写法,请务必参考。
●…… Jenkins 插件的配置
能够在 Maven 中执行 Cobertura 后,接着对 Jenkins 进行配置。Jenkins 中提供了 Cobertura 的插件,和安装 Git 的插件一样,请从插件管理进行安装。
安装完成后,在任务配置的构建后操作中会增加选项“Publish Cobertura Coverage Report”,选中该项。在出现的配置栏(图 5.12)中输入 **/target/site/cobertura/coverage.xml ,其他的配置保留默认值即可。
图 5.12 Cobertura 覆盖率报表统计
这样 Cobertura 的配置就完成了。Jenkins 会将覆盖率的报表以美观、易懂的形式呈现出来(图 5.13)。
图 5.13 Jenkins 的覆盖率报表
Jenkins 下能够看到每种度量方式的覆盖率变化的图表,以及更进一步的每个 class 文件的覆盖率。开始统计覆盖率后,开发人员编写测试代码的积极性也会有所提高。看着每次添加测试覆盖率都会有所提升,这是一件很有成就感的事情。在提高团队开发的质量的过程中,维持开发人员编写测试代码的积极性是相当重要的要素,从这一意义上来说对覆盖率的统计也是相当重要的。
5.4.8 静态分析
在自动化测试和统计代码覆盖率的基础上,若能对代码实施静态分析,就可以进一步提高团队开发的质量。因此应通过 Jenkins 持续地检查代码是否符合编码规则、是否容易产生 bug 等。
能够在 Jenkins 中使用的静态分析工具有以下这些。
Checkstyle 52
PMD 53
Findbugs 54
52 http://checkstyle.sourceforge.net/
53 http://pmd.sourceforge.net/
54 http://findbugs.sourceforge.net/
各个工具都有自己的特点。Checkstyle 擅长编码规则的检查等;PMD 的可定制化的程度高,对编码规则以及潜在的 bug 都能够检查;Findbugs 则以善于检查潜在的 bug 为特点。各个工具都支持规则的定制和扩展。请根据项目的具体情况选择合适的工具。
使用 Maven 作为 build 工具的话,静态分析工具和 JUnit、Cobertura 一样,安装 Maven 的插件即可使用。当然在 Ant 等上面也可以使用。Jenkins 的话同样只要安装插件就能使用。
通过 CI 实施静态分析的优点有很多,其中最显著的是可以减轻代码 review 时的负担这一点。代码 review 虽说是为了提高质量而需要实施的实践项目之一,但其实施的成本之高是无法忽视的。而如果能确保静态分析自动化,review 时就可以忽略对编码规则等细节的检查,从而对更为本质的内容进行 review。
面向 Java 这样的静态类型语言的静态分析工具还是比较多的,但面向动态类型语言的工具就比较少了。特别是要通过 Jenkins 来统计的话就更难了。即便如此,还是有面向 JavaScript 的 JSLint55 等几款可以通过 Jenkins 进行统计的工具,可以根据需求试着找一下。如果没有的话,自行制作也不失为一个不错的方法。
5.4.9 配置通知
最后对 build 结果的通知进行配置。包括插件在内,Jenkins 提供了多种通知 build 结果的手段。可以通过邮件、IRC、Twitter 来通知,还可以利用称为 XFD(eXtreme Feedback Device)的硬件进行通知。特别是 XFD,经过精心的设计,XFD 在 build 失败时会鸣响警报或发射玩具火箭等,非常有趣。
这里讲解一下通过邮件来通知的方式。Jenkins 默认提供了邮件通知的功能,只需要在“构建后操作”中选择“E-mail Notification”即可(图 5.14)。
图 5.14 邮件通知的设置
在收件人(Recipients)处填写邮箱地址后,在 build 失败或者失败的 build 被修复时就会收到邮件通知。可以在此配置团队的邮件列表等。默认设置中选中了“每次不稳定的构建都发送邮件通知”,这样一来,在连续 build 失败的情况下,每次都会收到通知邮件。通常这样设置是没有问题的,但如果项目经常 build 失败、状态不好,解除这个选项就不会每次失败时都收到邮件了。但这里并不建议解除该选项。
如果选中“单独发送邮件给对构建造成不良影响的责任人”,那么除了刚才在收件人处配置的邮箱地址之外,还会给造成 build 失败的当事人发送邮件。使用该功能需要预先为 Jenkins 配置邮件服务器。
