持续集成是一种软件开发的实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。
持续集成指的是,频繁地(一天多次)将代码集成到主干,通过持续集成流程的进行自动化方式的构建,编译和测试,提供可以部署发布的单元包
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。
它的核心措施是,代码集成到主干之前,必须通过自动化测试。
只要有一个测试用例失败,就不能集成。
Martin Fowler说过,"持续集成并不能消除Bug,而是让它们非常容易发现和改正。与持续集成相关的,还有两个概念,分别是持续交付和持续部署。
1、降低风险,由于持续集成不断去构建,编译和测试,可以很早期发现问题,所以修复的代价就少;
2、对系统健康持续检查,减少发布风险带来的问题;
3、减少重复性工作;
4、持续部署,提供可部署单元包;
5、持续交付可供使用的版本;
6、增强团队信心;
持续集成一般的做法: 通过svn或其他工具拉取代码->自动化构建->自动化编译->自动化测试->自动化部署->自动化发布->邮件发送通知;
持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。
持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。
持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。
持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。
持续部署的前提是能自动化完成测试、构建、部署等步骤。
测试是持续集成流程中重要的一环,也是区别去传统的软件开发流程中的一个重要的标志。为什么要有持续集成测试呢?
1、可以早点发现bug,这就是fix bug代价比较小
2、可以平滑产品,提高产品质量
3、可以让团队的每个人了解产品的质量状态
4、每天都有持续集成测试的报告发布
5、开发者对自己提交的代码测试情况有比较清晰的了解
6、可以有效地解决在QA人手不足的情况
7、尽可能地把测试自动化,让持续集成测试系统去执行这些自动化测试的case
1、打包,等待maven编译打包
2、发布测试环境,手动重启服务
3、通知测试组测试(邮件、用嘴巴喊等等方式...)
4、一顿grep查Exception,修复BUG,然后重复1、2、3、4
5、到达特殊的日子时,配合运维部署团队到测试环境手动copy最新版WAR包到生产环境,23点的一瞬间执行一个脚本,时刻盯住脚本运行结果,最后验证
我们可以发现很多问题:
1,编译打包的过程浪费开发资源,一次测试部署正常10到20分钟,那出现问题的情况...
2,测试长时间怠工,资源利用不充分,处于一人干活多人旁观低绩效状态
3,研发与测试的沟通方式高成本低效率
4,BUG反馈方式低效
5,生产环境得不到有效的管控以及安全保障,人工浪费如果产品或者销售想要给客户演示测试环境,得到的结果可能是测试暂时不可用或者稍微等15到20分钟,是否能计算出他们的心理阴影面积?
DevOps的中心思想在于提高产品各个阶段的产出效率减少或者避开团队间的沟通障碍,推动产品的快速迭代,“快速失败”,从而实现持续交付、持续部署。而持续集成只是DevOps中的一个环节,下图清晰描述了CI各个周期活动。
我们可以发现较多优点:
1、流程全自动化,减少重复性的手工操作
2、持续发布测试,时刻保持可发布的产品
3、团队、高层对项目、产品的进展清晰可见,把控风险
4、资源效率有效利用,流动效率更快
因此,我们要做到持续集成,我们需要:
1、一套持续集成工具,大体可分为云集成与本地化集成系统,云集成比如Travis CI、cloudbees的云集成等,本地化集成主要是开源Jenkins的搭建,如果需要大规模部署Jenkins且有预算可使用Jenkins商业版
2、自动化测试工具、良好的测试用例编写
3、版本控制系统,git、gerrit推荐
4、构建、测试失败反馈机制,邮件、自动化运维(AI...)、日志收集分析系统
5、一套需求、产品、开发、测试、部署、运维共同使用的敏捷研发管理系统,市场上有阿里云效、腾讯的TAPD等
根据持续集成的设计,代码从提交到生产,整个过程有以下几步。
1 提交
流程的第一步,是开发者向代码仓库提交代码。所有后面的步骤都始于本地代码的一次提交(commit)。
2 测试(第一轮)
代码仓库对 commit 操作配置了钩子(hook),只要提交代码或者合并进主干,就会跑自动化测试。
测试有好几种。
单元测试:针对函数或模块的测试
集成测试:针对整体产品的某个功能的测试,又称功能测试
端对端测试:从用户界面直达数据库的全链路测试
第一轮至少要跑单元测试。
3 构建
通过第一轮测试,代码就可以合并进主干,就算可以交付了。
交付后,就先进行构建(build),再进入第二轮测试。所谓构建,指的是将源码转换为可以运行的实际代码,比如安装依赖,配置各种资源(样式表、JS 脚本、图片)等等。
常用的构建工具如下。
Jenkins
Travis
Codeship
Strider
Jenkins 和 Strider 是开源软件,Travis 和 Codeship 对于开源项目可以免费使用。它们都会将构建和测试,在一次运行中执行完成。
4 测试(第二轮)
构建完成,就要进行第二轮测试。如果第一轮已经涵盖了所有测试内容,第二轮可以省略,当然,这时构建步骤也要移到第一轮测试前面。
第二轮是全面测试,单元测试和集成测试都会跑,有条件的话,也要做端对端测试。所有测试以自动化为主,少数无法自动化的测试用例,就要人工跑。
需要强调的是,新版本的每一个更新点都必须测试到。如果测试的覆盖率不高,进入后面的部署阶段后,很可能会出现严重的问题。
5 部署
通过了第二轮测试,当前代码就是一个可以直接部署的版本(artifact)。将这个版本的所有文件打包( tar filename.tar * )存档,发到生产服务器。
生产服务器将打包文件,解包成本地的一个目录,再将运行路径的符号链接(symlink)指向这个目录,然后重新启动应用。这方面的部署工具有 Ansible,Chef,Puppet等。
6 回滚
一旦当前版本发生问题,就要回滚到上一个版本的构建结果。最简单的做法就是修改一下符号链接,指向上一个版本的目录。
实现持续集成,一般需要以下内容:
1. 具有版本控制功能的代码库
例如:SVN, Git。相信现在的项目没有不对代码进行版本管理的,所以这方面内容大家也应该非常熟悉。在这里不再详述。
2. 构建工具
在持续集成的过程中,需要对已存在的或者新提交的代码进行编译、打包等操作。这样,就需要构建工具帮助构建一个编译环境,并对代码进行编译、集成、打包等操作。而构建的方式越简单越好,最好是一句命令就可以启动构建。现在,各种语言都有自己的构建工具,例如Java中的Maven、Gradle、Ant,前端中的Grunt、Gulp等等,好好利用这些工具,就能帮你完成这部分工作。
3. 测试
测试是持续集成中重要的一环。代码提交前,需要在本地运行单元测试,通过测试后再提交代码。构建完成后,需要运行全部测试(单元测试,功能测试,端到端测试)以确保产品质量。如果有一个测试没有通过,那么这次提交的代码不能进入主干;或者这次构建的产物是一个失败的构建品,不能用于发布。另外,由于持续集成依赖于这些测试去保证产品质量,所以测试的覆盖率要尽可能高。测试覆盖率不够高(包含代码覆盖率和功能覆盖率),就无法充分反映代码的变动是否对系统带来影响。而低覆盖率的测试,压根就无法保证产品质量。当上线的时候才发现问题就太迟了。
4. CI工具
CI工具的作用是将整个CI过程管理起来并自动化,结果可视化。部分工具还结合了CD(持续交付)的功能。现在已经有很多CI工具去满足你不同的需求,例如Jenkins,专为Github开源项目提供的Travis,.Net用的CruiseControl.Net。他们各有特色,根据自己的需求选择适合自己的工具即可。
好了,当集齐以上内容后。我们用一个例子来介绍一下一个典型流程是怎样的。
假设我们现在有一个产品P,以war包形式发布,由三个模块module A, module B, module C构建而成。三个模块的关系为:A和B为独立模块提供不同功能。C依赖A和B,然后构成产品P。我们使用了Git作为我们代码库的版本管理工具,用Java进行开发,maven作为我们的构建工具。在每个模块里,有我们基于JUnit写的单元测试代码。独立于三个模块外,有一块代码,也是基于JUnit写的,作为我们的功能测试代码(集成测试)。
集成代码
当我们完成开发工作,需要提交代码到代码库前,我们至少需要在本地跑一次单元测试,在保证全部测试通过后,才可以将代码提交至我们的代码库Git上面去。例如,在我们上面描术的项目中,我对module A的代码进行了修改,那我最起码得在本地运行一次mvn test(执行Maven命令,test代表将会执行到maven default生命周期中从validate到test阶段), 执行成功后,我才会将代码commit and push到远程Git库上去。要做到这样效果的话,就需要保证单元测试代码也同步完成。而在极限编程中(XP),人们比较倾向于测试驱动开发(TDD, Test-driven Development)的实践,通过提前写好自动化的单元测试,保证好每一步功能开发的质量。
自动构建
通过CI工具,可以设置一个勾子,当代码提交后触发相应构建。例如,我们提交了module A的代码时,Jenkins会扫描到我们这次提交,勾子触发module A的构建。这个过程会做如下操作:
1,Jenkins调用Git插件,从Git库上下载最新代码;
2,Jenkins调用Maven插件,执行Maven命令(一般为mvn install,如果需要上传至远端Maven库,也可以执行mvn deploy)对该模块进行构建。经过编译、通过单元测试后,便可以打包并安装到本地Maven库,以供其它依赖所用。这次构建成功,意味module A在模块自身的单元测试范围内是正常的。
3,因为module A是包含在产品P里面,所以,也需要回归一产品功能测试。由于module C依赖A,并构建成产品。所以在CI工具里面,我们需要配置好在module A构建成功后,自动触发module C的构建,经过类似步骤1、2这样的构建后,最终会生成产品P的war包。而C的构建成功,只代表着通过了module C自身的单元测试,还不能对生成的war包进行功能测试。然后就要看我们下一步的工作--自动部署了。
自动部署
在功能测试之前,我们需要在CI工具里配置一项任务,用于将最新构建出来的产品包部署到测试工环境中去。这个任务由产品构建任务成功而被触发,而部署方式根据不同使用方式及不同的实际情况而多种多样。例如通过脚本将新构建的war包上传至指定位置,等待web容器自动扫描及部署。能或者产品有自己的安装脚本,我们在任务中配置好运行安装脚本,就可以自动将产品部署到指定的测试环境中去。
功能测试(集成测试)
当部署成功后,真正的功能测试就可以开始了。一般情况下,我们可以独立出一块代码,基于JUnit编写好我们的功能测试代码(JUnit是作为测试的入口以及基本测试框架。如果你的需求比较复杂,那你完全可以将其它三方框架与JUnit集成使用)。功能测试过程和构建过程非常相似,均是依赖Git和Maven去完成:
1,Jenkins调用Git插件,从Git库上下载最新代码;
2,Jenkins调用Maven插件,执行Maven命令:mvn clean test。
区别在于功能测试阶段,Maven只执行到default生成周期的test阶段,不会执行后面的package和install。因为它只需要Maven帮忙运行测试代码即可,它本身没有什么可以构建的。
P.S.
如果还需要更复杂的端到端测试的话,可能就需要准备更复杂的部署脚本,或者预先准备好整套端到端测试环境,后面只需要部署好war包即可。但无论怎样,最终原来理还是相同。
交付
当新提交代码后构建出来的产品包,通过了各种各样残酷的测试后,就说明这个包是稳定的,能达到基本交付条件的(前提是自动化测试的覆盖率足够高,当然,有一些极端的情况需要人工测试的另说)。那么,我们就可以将这个包放到指定目录作为交付品,供其它测试团队获取并进行进一步的测试,甚至供生产环境部署使用。
持续集成作为极限编程中的一个实践,现在已被很多公司使用。但是,使用持续集成,并不是说你得接受极限编程的全部东西。相反,它可以独立开来,与其它实践结合使用。
持续集成是敏捷开发中快速迭代的重要保证。
自动化测试是持续集成中重要一环,要真正用好持续集成,就要尽量提高自动化测试的覆盖率。
以上两种流程A,B都是一些公司常用的模式,大同小异,大家仅供参考。