持续集成(一):基础篇
引言
持续集成,简称CI,是一项由Grady Booch提出的技术,它鼓励开发者们持续不断地将他们的代码合并到主干源码仓库。
这些'合并'或者'提交',每一次并入到仓库通常都伴随着执行一系列的自动化任务:代码的编译,单元测试和集成测试的执行,评判代码质量是否下降的静态代码质量分析等等。
这些自动化任务有助于验证新代码的合理性,并分辨出新代码是否会造成任何破坏,例如,集成方面是否有遇到问题?最重要的是,开发者们可以在提交代码变更之后快速及时地收到这一反馈。
拥有CI设施和流程被视为是绝大多数现代软件公司的一个基本要求 —— 无论组织是否采用敏捷方法,DevOps,或者两者兼而有之。
由于能够为开发者和客户两者带来可观的收益,CI已然成为任何软件开发流程里的必要部分。这些可以看到的收益包括软件质量,更快交付到市场,更快的缺陷反馈周期,更低的开发成本,以及整个软件流程里集成问题发生频率的降低。
敏捷里的CI
敏捷开发 —— 过去十年来基于十二项原则建立起来的最常用的软件开发方法;这十二项原则里的其中三项强调了快速开发周期和及时的反馈循环:
客户是满意的,他们得到了更早并持续交付的有价值的软件
可工作的软件被定期地交付(以周计,而不是月)
可工作的软件是整个流程的衡量准则
CI流程和一些工具是用来实现这些众多目标的一部分。
在一个鼓励早期反馈循环和持续交付可工作软件的环境里,CI使得开发者们能够通过自动化质量保证和构建流程,带着信心持续地提交变更。借助CI设施,每一次签入仓库的集成问题以及代码质量的可接受水平都能够被验证。
DevOps里的CI
'DevOps'这个术语被用来描述一组为了提高整体软件项目的交付效率而将'开发'和'运维'整合到一起的实践及方法。
最近几年,DevOps在许多组织中获得了很多关注,尤其是那些处理构建大型复杂软件的企业。
DevOps是一种文化上的转变,它鼓励组织内的不同部门 —— 开发,测试和IT运维相互沟通和协作。DevOps的目标不是单靠一款工具就能实现的。这里有一组共同组成所谓“DevOps工具链”的各个阶段,每个都有其单独的目标。依赖于组织的选择,存在着各种各样的工具可以让我们实现那些目标。
CI流程涉及到该工具链里的两个阶段,即创建和验证。创建阶段包括代码开发,将代码签入版本控制系统等。验证阶段则包括自动构建产品并对其进行测试。
DevOps文化的其余部分则是基于这两个基本组成。没有CI的话,很难看到敏捷开发的好处,也几乎不可能创造出DevOps文化。
通过CI流程建立的开发工作流
通过上述对于CI如何结合敏捷开发以及DevOps方法的介绍和基本了解,现在我们将进一步介绍一个典型的CI流程,它用在主干代码库新功能的引入。
1、签出代码
开发人员最开始首先从源码管理系统里签出或者克隆最新的代码到本地开发机器。随后,开发人员继续基于主干分支创建出一个新的功能分支。该分支将专门用于引入仅在新功能范畴内的更改。
2、开发新功能
这一阶段即编写新功能所需的代码。
请记住,代码的更改不仅有功能代码的变动,还包括新代码的单元测试和集成测试。
3、早期集成并且持续进行
在一个团队里工作时,可能有众多开发者正在同时开发多个功能特性,然后提交变更到主干分支。如此一来,新功能因为和主干分支不同步而落后的时间便会变得越来越长。
因此,建议开发人员经常保持他们的代码和主干代码分支的内容同步。该步骤将会把主干分支的变更带到功能分支,所以除了自己的功能代码之外,开发人员总是使用主干分支代码的最新副本。
开发人员必须在他们的功能分支上执行自动化测试。如果这些测试或者代码本身编译失败的话,他们必须修复这些集成问题。
和主干分支的同步会一直被保留,等到功能被完整实现,新的代码会被发起集成到主干代码库。
一般来说,建议每个开发者从主干分支同步到他们的功能分支至少一天一次。
4、自动构建和验证
当有变更提交到主干分支时,无论他们的变动有多小;它将会发起一次自动构建流程,编译代码然后执行单元和集成测试。
这个过程会验证新代码有没有破坏任何现有正在工作的代码。
当然,它也假设在这个阶段运行的由开发者他们自己提供的所有测试都是之前步骤的一部分。
因此,即便显而易见,我们仍然需要强调的是,开发人员必须提供足够的测试用例来验证代码的可执行性。在没有合适的测试用例的前提下,验证这一过程将无法达到必要的水平。这些测试覆盖的代码量称为代码覆盖率——更高的覆盖率,意味着更好的验证效果。
5、将变更提交到主干分支
当功能被认为是完全满足开发者和相关人员的需求时,功能分支的代码便需要被合并到主干分支。
在做这件事情之前,功能分支需要和主干分支再同步一次而开发者需要确认该次构建和验证步骤没有错误,全部通过。
如果开发者经常集成的话这一步会更轻松并且没有压力,否则的话它通常会变成一个合并冲突的噩梦,即出现开发者们一起协同工作的代码仓库,其代码存在冲突并且针对相同的文件有覆盖变动的情况。
用户会发现上述步骤同之前提到的敏捷开发和DevOps的目标是多么的相似而且点明了它们的主旨!
优点
引入CI的优点有很多,主要是关于代码变更以及代码质量的快速反馈(无论正面还是负面)。
自动化验证代码变更的过程。通常这些步骤在开发人员的本地机器上也可以通过一条命令手动执行!
在软件开发的早期发现缺陷
在早期发现与其他代码和组件的集成问题
自动化测试代码的能力
由于代码质量的提升,客户也因此感到很愉悦
能够上线一个软件开发的一小部分,验证每个位是否完全正常运行,没有错误
能够针对每次提交以及整体代码引入衡量代码质量的手段,并且对代码质量把关
缩短开发复杂软件的市场交付时间
减少软件开发的大爆炸方式造成的代码合并冲突和无法预料的行为,即所有内容大块地合并到主干分支的情况(通常在一个项目生命周期结束的时候)。
难点
用"缺点"一词来描述这部分内容的话可能不是一个好选择,因为CI只会给每个人带来好处(不仅仅开发者,还有客户),所以我更倾向于使用"难点"一词。
不经历重大的返工的话,遗留系统的架构可能无法支持CI;
很难对遗留代码引入CI。遗留代码大都缺少自动构建系统的支持。一般它们都是手动发起构建或者是以一个半自动的方式。为了克服这一问题,通常我们需要为此引入一个重度耗费人力物力的现代构建系统。任何希望迁移到CI的遗留系统首先考虑的应该是将引入一个新的自动构建系统作为起点;
遗留代码一般都缺少自动化测试。这将会使得它很难或者根本不可能检查已经提交到主干分支的新代码的正确性。CI的主要目的(即早期发现问题)没有测试是不可能办到的;
在文化上,组织可能没有采用敏捷原则或DevOps的工作方式。在开发周期里,持续不断地提交到仓库这件事情可能不会发生,从而使得CI变得没有意义。除此之外,每个组织也有它的政治因素在内,使得在实现CI的过程中可能遭遇各种各样的磨难。
小结
笔者已经介绍了CI的基本原理,为什么需要它,采用的典型流程是什么,收益以及短板等等。
大体上来说,企业组织利用CI不仅仅只是为了编译代码和执行测试,还可以确定其他可衡量的方方面面,比如代码质量,代码覆盖率,使用自动化测试衡量的性能等等。
一些组织也会去拓展CI的用例场景,不仅仅只是用于验证代码的可用性,还用于在一个测试或者生产环境打包和安装构建。这被称为持续交付或持续部署。
持续集成应该是大多数公司制作复杂软件的目标。一旦CI的基本设定到位,开发流程便不会再有什么特别的开销。如果要说有什么变化的话,大多数团队都会发现,引入CI流程和相关工具将会使得集成问题大大减少,并允许团队更快速、自信地开发软件。
继续前行——CI服务器及工具化
上述内容里笔者没有提到辅助实现CI的一些工具,因为这是一种除了自动化构建之外不需要特定工具的实践。拥有一个源码管理仓库以及一个持续集成服务器是有利无害的。CI服务器和其他一些相关工具的内容将会在持续集成系列的下一篇文章里介绍。
持续集成(二):CI服务器 & 工具集
【编者的话】这是持续集成系列的第二篇,在本文中,作者介绍了CI流程的具体实现。
引言
在持续集成(CI)系列的前面一篇文章里,我们关注了CI的基本概念,它是如何帮助企业减少集成软件时的错误,采用它带来的好处和遇到的难点,以及最后,它是如何助力敏捷开发和DevOps团队文化的推行。
我们还讨论了CI落地到开发流程最常用的一些方式,最终能够带来软件质量上的提高,更快的市场交付,更快的反馈周期以及更低的开发成本。
如果你还没有读过前面一篇文章的话,不妨在继续阅读本文之前先翻阅一下!
在本文中,我们会介绍到一些工具,它们将有助于为组织实现一个强大并有价值的CI流程。
持续集成服务器
实现一个CI流程的唯一必要条件便是得有一个自动构建系统。
然而,实际上,除了自动构建系统外,安装和配置一个“CI服务器”也是相当有帮助的。
概览
CI服务器会扮演核心大脑的角色在幕后工作,将各种行业标准的实践无缝整合到CI流程的实施中。
初始化CI流程
通常,当开发者将代码签入到源码仓库时就会发起一个CI流程。
CI流程的工作流也可以通过其他方式触发,针对于开发团队而言,一般来说以下方法均可选用。
手动 —— 无论是通过CI服务器的管理界面还是脚本,用户可以手工执行CI工作流
计划任务 —— 预配置好的计划,例如一次凌晨的构建
跟踪触发式 —— 在每次提交到源码版本管理系统时触发
凌晨的构建一般需要针对代码执行更大规模的检查,并且花费更长时间来完成检测。
拉取最新代码
作为整个流程里的第二步,CI服务器会负责从源码管理拉取最新的代码。这可以借助poll或者push机制实现。
使用poll机制的话,该CI服务器会配置一个源码管理服务器的位置,以及它的安全证书。它会根据一个时间间隔定期地轮询指定位置,以检测是否有发生任何新的签入以及代码变更。一旦检测到有变更,它会从源码管理服务器下载代码最新的副本到本地磁盘。
使用push机制的话,源码管理系统会配置一个'钩子'指到CI服务器。当开发人员提交了一个变更到仓库时,之前配置的钩子将会被调起,而它会让CI服务器知道,这里发生了一次变更。
构建
源代码一般是自包含构建的,即CI流程所需的构建脚本是放在源码仓库里的。正如在之前的步骤里详细介绍的那样,一旦最新的代码被拉取下来,一个捆绑好的脚本将会被用来触发该次构建。
针对Java ™项目,构建自动脚本一般会采用Maven或者Gradle。人们不大常用那些不支持内置依赖管理的构建系统。万一遇到不需要依赖管理的情况则可以试试"make",一个在类Unix系统被广泛使用的构建机制。这里有各种各样的其他构建工具可供选用。一些已经列在了构建自动化软件的Wiki里。
执行测试
在CI流程的这个阶段里,单元测试和集成测试将会被执行。一般来说,这些测试也会被打包到代码里。
针对基于Java ™实现的系统而言,这些测试会通过一个像JUnit这样的测试框架来执行,从而可以轻松模拟它们的一些测试依赖。
某些编程技术可能有它们自己的测试运行框架,Spring的Spring Junit Runner,Java EE的Arquillian等。
在CI服务器上运行测试主要有下面这些好处:
你曾经有没有遇到过这样的场景,这些测试在一台机器上是通过的,但是在其他机器上却失败了?通过在一台中央服务器上执行这些测试,我们可以消除一些测试环境方面的问题
针对每一次变更都会立即触发执行测试,而且如果有任何新的代码变更打破了预期的行为的话,你能够马上知道
大多数团队都是并行地在开发着许多功能,甚至可能一些团队成员也是分布在全球各地。每当开发人员提交代码到仓库时,任何故障都可以被识别出来并且立即修复。
结果
在CI流程的最后,只可能存在两种结果的其中一种 —— 要么构建和测试失败了,要么通过了。
每一次签入都会被验证并且确保它不会破坏现有的代码。代码在它被合并到主干分支后不久会被构建和测试。这将可以降低主干代码崩溃的频次。
一般来说,CI服务器会配置成在遇到故障时发送邮件(发给团队里的每一个人或者仅仅单独抄送负责上一次签入的相关人员)。通过这种方式,可以快速知晓故障并且尽快采取更正措施。
大多数CI服务器还会突出展示最近一些构建的状态而且最近几次构建的状态还会用红-绿-琥珀色指示灯来标明。
扩展CI服务器
前面一节着重讲述了CI的基本工作流。然而,大多数组织也会根据自己的优势将它用于更多的用途。CI服务器可以通过安装各种"插件"来拓展行为,从而实现功能上的扩展。下面介绍的是一些最常见的扩展。
技术债 & 代码质量
任何引入到软件里的变更将导致系统复杂性的提高以及混乱程度的加深。系统的混乱程度被称为“技术债”。每当新代码被引入到系统时,技术债就会相应增加。如果技术债长期不受重视的话,得到控制权的可能性将变得越来越渺茫,毕竟越来越多的功能在紧迫的期限内堆积过来。而这将会对软件的生产力和可维护性产生负面影响。
迭代开发方法,将测试的执行自动化,以及使用CI来监控每一次签入的技术债,这些是保证技术债在可控范围内的不二法门。
我们应当使用一些工具,比如SonarQube,一个分辨代码质量和跟踪技术债的开源平台。它可以轻松地集成到任何CI服务器,并为用户提供团队技术债实时数据的展示。
引申阅读请转到:
代码语义
对于开发人员而言,引入CI在另外一个重要方面同样有价值 —— 它可以用于衡量代码质量 —— 语义,以及一些常见的反模式。
静态代码分析工具可以充当CI流程的一部分,以洞察代码的健康状况。历史数据也可以存储起来,从而提供一个时间段内代码质量的衡量。用户可以在两个提交之间列出比较,以确定每个提交引入的债务度量。
许多现有的工具可以对代码做静态分析并且计算出各种度量代码质量的指标。这些工具可以并入到持续集成服务器然后自动执行。
一些更加常用的工具有:
Checkstyle
Findbugs
Sonar
有关各种平台工具的详细列表,请参考静态代码分析工具的列表。
代码审核
正如在前面的文章里介绍的那样,开发人员工作在功能分支上,并且会尽可能多地提交代码。
我们可以在持续集成服务器里使用代码分析工具(例如Sonar)来执行自动代码审查。随后每个开发者需要负责解决在他们的提交里生成的评论意见。
自动代码审查是基于一组预定义好的规则,并且是一个查找潜在技术问题的好办法。这些规则可以从所有主要的CI供应商处下载,并适用于大多数主流编程语言。
一旦自动审查的评论意见提出的问题被修复了,CI服务器会随即发起一个人工代码审查,揪出那些自动审查无法找出的问题,即验证业务需求,架构问题,代码是否可读,以及是否易于扩展。
CI服务器也可以配置成,如果某些人没有审查代码便阻止对主干分支的任何提交。最常见的做法是每个合并到主干分支的提交至少要有两个审查人员(reviewer)。在基于Java™的项目里,针对此用途最常用的工具是Gerrit。
有关其他编程语言代码审查工具的详细列表,请参考代码审查工具列表的WIKI。
无界面测试
如果要在CI服务器上运行用户的UI测试,那么他必须得依赖无界面测试,因为没有浏览器的显示界面来启动。无界面测试意味着在没有图形用户界面的情况下运行UI测试。这样的测试需要一个无界面浏览器,它和市面上流行的web浏览器类似,但它是通过一个命令行接口执行的,并且拥有一个UI元素的内存模型。该内存模型用来模拟和UI的交互,比如在UI上一个按钮的一次点击,会被模拟成内存模型对象的一个行为。
市面上流行的测试工具,比如Selenium可以被用来做无界面测试。像PhantomJS这样的无界面浏览器也被广泛使用。通过在构建里引入这些测试,CI服务器将能够验证UI方面的故障。
安装CI服务器
CI服务器的安装方式有三种:
单机
托管
私有云
单机版的CI服务器安装即是在一台单个主机上完成。这通常是用于小型项目或者小于10个研发的团队。
托管的CI服务器可以在公有云平台上看到,一般是根据订阅计价。往往这些是和CI服务器可以访问的云端源码管理系统紧密结合在一起的。这一般被那些不想维持和运维CI基础设施的企业所采纳。对于中型团队来说,这也是一个非常可行的上手CI的选择。
一些大型企业以及那些想要完全掌控及安全使用他们自己的基础设施的组织,选择的则是私有云的部署方式。一般来说,他们会配置一组高性能的服务器用来运行多个CI服务器的客户端,以支撑数以百计的开发人员签入其代码的繁重工作负载。
一些最广泛使用的CI服务器有:
Jenkins
Travis CI
TeamCity
CruiseControl
有关CI服务器的详细列表,请参阅持续集成软件的比较。
小结
在这篇文章里,我们已经介绍了如何使用CI服务器来创造高质量的产品。CI服务器可以扩展成自动测量许多指标,例如技术债,代码语义,测试覆盖率等。
签入代码时代码质量的即时反馈确保故障可以更加及时地被发现,从而保证客户可以获得一个可靠且正常工作的产品。
将持续集成引入开发过程还有许多其他的好处和出发点。 这里也有许多其他工具来扩展CI服务器。敏捷软件开发及DevOps方法与持续集成天然地相辅相成。
那么,如果你还没有把持续集成纳入到你的开发流程里的话,现在还为时不晚!
下一篇
在持续集成系列的下一篇文章里,我们将介绍到实现CI的一些关键模式和反模式。透过这些建议,你将能够实现适合你所在组织开发目标和策略的最佳流程。
【编者的话】这是持续集成系列的最后一篇,在本文中,作者列出了Martin Fowler撰写的CI白皮书里面的一些原则,并介绍了一些个人的实践经验。
本文讲的是持续集成(三):最佳实践这是持续集成系列的第三篇。在这篇文章里,我们将介绍实现一个CI流程的一些最佳实践。笔者也将会根据自己的行业经验介绍一些真实世界里的提醒和警告。
快速回放:在本系列的 第一篇 里,我们介绍了CI的基本概念以及它和敏捷开发及DevOps团队文化的关联。在 第二篇 里,我们介绍了CI服务器的概念以及它是如何将各种实现一个CI流程的行业标准实践无缝整合到一起。
如果你还没有读过前面的文章的话,笔者强烈建议在继续阅读本文之前先翻阅一下!
Martin Fowler,在他的CI 白皮书 里提到了一些应当成为任何CI设定一部分的关键实践。这些建议多年来已然成为"这样"一组持续集成的最佳实践。同一主题的 维基百科 页面则对外展示了Martin Fowler所阐述的那些原则的本质。
下面,笔者将以自己个人的视角和大家一起来看看这些最佳实践里的每一项。
维护一个单一的源码仓库
"这种做法主张对项目的源代码使用一个修订版控制系统。所有需要用来构建该项目的素材都应该放到仓库里。按照惯例,采取这样的做法并且是在一个修订版本控制社区里,该系统应该是可以基于一个全新的签出做构建,而无需任何额外的依赖。极限编程倡导者Martin Fowler还提到,在工具支持分支的情况下,对它的使用应该最小化。相反,我们推荐的是将变更集成进来而不是同时维护软件的多个版本。主线(或者主干)应该是软件可工作版本代码的存放位置。"
提醒
该原则不能单从字面上去理解,这并不意味着你只需要一个单个的仓库。这里的关键是所有构建项目所需的素材都可以在一个仓库里找到。该项目应当可以基于一个全新的签出构建,并且不需要额外的依赖或者手动步骤。
这里'项目'的定义取决于你,如果代码仓库很小的话它可能意味着整个可交付的产品,或者可以是组织好的代码里任意逻辑模块或者组件。
警告
该原则原本建议不要在版本控制系统里使用分支。相反,它建议项目由始至终仅在一个单一分支下开发。
不过,笔者并不赞同这一点。在绝大多数组织里,在多个分支下并行开发是很有必要的。企业往往需要支持产品之前发布的版本,修复其中的错误,而其他的团队成员则开始下一个版本的工作。这就需要在代码库里维护多个分支。
构建自动化
"构建系统应当一条命令就能办到。许多构建工具,比如make,已经存在很多年了。其他更多近期涌现的工具经常用在持续集成的环境里。构建的自动化应该包含自动集成,这通常包括部署到一个类生产环境里。在许多情况下,构建脚本不仅可以编译二进制文件,还可以生成文档,网站页面,统计信息和发行版媒介(如Debian DEB,Red Hat RPM或Windows MSI文件)。"
提醒
构建的自动化应当包括诸如编译代码,执行单元测试以及集成测试等步骤。 它们也许还包括许多其他工具 - 如前面文章里描述的代码质量检查,语义检查,衡量技术债等。绝大多数现代构建工具都支持这些额外的集成,而且应该可以用于建设持续集成环境。
在现实世界的项目里,不同的团队可能负责开发系统的不同部分,每个团队都拥有自己的仓库。 在这种情况下,几乎不可能(没有重大工作的话)而且也完全没必要基于整个产品做自动化构建。 一般来说,为系统的每个单独部分开发自动构建就足够了。
警告
定义CI流程的目的,即除了自动化构建流程外,是否还有其他的投入点?作为CI流程的一部分,你计划测量哪些指标。很多时候,笔者见到的是CI设定被视为单独只是开发人员的工具。
延伸之前一点的话,CI不是敏捷开发/DevOps,它们只是针对整个组织成功实施CI流程所使用的工具之一。敏捷开发/DevOps的范式可以超越软件开发的技术层面,并扩展到组织的文化里。
让构建自检
“一旦代码被构建,所有测试都应该被执行以确认它的行为如开发人员们预期的那样。”
提醒
代码应当至少包含单元测试。像JUnit这样的框架可用于轻松地模拟依赖。
特定组件与其他模块的交互应当被模拟取代。 这可以确保一个模块能够独立于其他模块进行测试。
警告
单元测试应该测试行为,而不是实现细节。有什么区别呢?我们不妨通过一个例子来说明:测试行为的话:"我不关心你怎么计算汽车的速度,保证答案是对的就行",如果是测试实现细节的话:"我不关心答案是什么,只要确保你采用的是这个公式:速度=距离/时间即可",测试行为是正确的方法,因为我们只需要验证结果就行了,而不是方案是如何实现的。
许多测试框架允许我们声明模拟对象,即测试模拟对象是否被调用,是否在特定参数中被传递。这些资源应当被最小化,除非实现本身的测试是主要的关注点。
人人每天都提交到基线
“通过定期提交,每位提交者都能借此减少冲突变更的次数。一周工作产出在签入时与其他功能冲突的风险可能很难解决。在更早期的阶段,系统某块领域的小冲突会促使团队成员就其所做的改变进行沟通。至少每天提交一次更改(每次创建一个功能)通常被认为是持续集成定义的一部分。此外,一般建议每晚进行一次构建。这些是下限;业内持续集成的典型频率预计会高得多。”
提醒
代码应至少包含单元测试。像JUnit这样的框架可用于轻松地模拟依赖。
特定组件与其他模块的交互应当被模拟取代。
(原文这一部分的Tips可能存在谬误,译者注)
警告
已经完成的工作应当提交到主分支。主分支应当总是可工作版本的软件代码。
如果看到哪次构建失败的话请不要提交分支。你应该先验证下是什么导致的错误,然后尝试尽快解决而不是提交自己的代码。为什么在构建失败的时候不应该签入你自己的代码呢?首先,你自己的提交可能存在一些问题,它可能会破坏一些预期的行为。你不会知道这些问题是什么,除非得知上一次签入时构建的状态。而且每一次签入都有可能因为添加了现有的错误让问题变得更糟。
应当构建每一次提交(到基线的)
“系统应当构建每一个合并到当前工作版本的提交,从而验证它们集成地很好。常见的做法是利用自动持续集成,尽管这可以手动完成。对大多数情况而言,持续集成是采用自动持续集成的同义词,一台持续集成服务器或者守护进程会监控校订版本控制系统的变更,随后自动运行构建流程。”
提醒
用户应当分离主分支和其他分支的CI工作流。这些步骤包括从编译到打包再到测试。主分支的构建一般应当包含更多的测试。主分支的构建也可能需要运行不同的脚本,因为应用可能需要针对不同的部署平台打包成不同的格式。在其他分支上运行的构建可能根本不需要打包这一步,或者通常局限于与开发人员相同的平台的打包。
“夜间构建”也应当在每晚计划好的时间点执行。相比于其他分支而言,该构建应当包含更多的验证过程。它需要花费更长时间去运行并且执行频度更低。
警告
主线分支里不应该注释测试。将测试注释掉的话,我们得到的会是构建状态的错误提示。
引入编码标准的检查是CI流程的一部分。代码必须经过自动化工具以及团队成员检查,然后才能签入到主线。
保持构建速度
“构建需要快速完成,这样一来如果存在集成问题便会立马被识别出来。”
提醒
正如Martin Fowler所述,测试金字塔如下所示。用户的目标应当是拥有更多比例的可以快速执行的测试。这意味着相比于其他类型的测试,用户需要拥有更多的单元测试。
避免在单元测试中使用数据库。如果可以的话,避免将其用于集成测试。一般来说集成测试需要采用一个替代数据源,通常指向的是一个内存数据库。如果不可避免的需要使用真实数据库进行测试的话,用户需要保证在每次测试之前刷新数据库,确保数据处于已知状态,并且测试不会基于不一致的数据开始。
警告
不要依赖大量的UI测试,UI测试是脆弱的,即他们是经常变动的,并且需要花费大量的精力去维护。笔者建议用户使用像Selenium这样的UI测试框架来规避UI测试过程中遇到的一些问题,例如UI元素在屏幕上位置的变动,UI事件的处理等。
克隆一个生产环境做测试
“存在测试环境的话可能会导致测试通过的系统部署到生产环境时发生故障,因为生产环境可能和测试环境有重大差异。然而,建设一个生产环境副本的成本是非常高昂的。相反,测试环境,或是一个单独的预发布环境('staging')应当被建设成实际生产环境的一个可扩展版本,在节省成本的同时维护技术栈的组成和它们之间的细微差别。在这些测试环境里,人们常常使用服务虚拟化以访问那些超出团队控制的依赖(例如,API,第三方应用,服务,大型机等),它们可能仍然在迭代发展,或是在一个虚拟测试实验室里的配置太过复杂。”
提醒
这是在现实世界的开发中付诸实践时最难实现的一个原则。这需要构建自动化系统来创建并将软件包部署到反映真实生产环境的一个灰度环境里。 除非用户的应用程序是自给自足的,没有任何外部依赖,否则的话这一点很难实现,毕竟,生产环境的复杂度很高。笔者对复杂产品的建议是投入时间和精力借助虚拟化平台或容器平台(如Docker)来复制生产环境。持续交付的流水线可用于将构建部署到这些环境。
获取最新的可交付成果变得很容易
“为测试和其他相关人员提供构建结果,可以在重建不符合需求的功能时减少所需的返工量。此外,早期测试可以减少代码缺陷在部署前的出镜机会。更早地发现错误,在某些情况下,可以减少解决这些错误所需的工作量。每一位程序员都应该从更新仓库项目代码开始新的一天。这样一来,它们都会保持最新。”
提醒
建议使用像Nexus这样的资源仓库来存放最新版本的软件包。通常,存储在这样的资源仓库中的包,它们也是通过版本号进行版本控制的。这使得所有参与者都可以轻松获得当前或过去的任意构建包。
警告
只有主线分支中的构建包才能存放在资源仓库里。 如果不这么做的话,每当有人新建一个正在工作的分支时会导致现有软件包被覆盖。
人人都可以看到最近一次构建的结果
“我们应当能够轻松找出构建是否有问题,如果是的话,谁做了相关的改动。”
提醒
所有的现代CI服务器都有能力展示包含构建状态的仪表盘。正如前面一篇文章所描述的那样,这些也可以被配置成展示其他的一些指标。
所有CI服务器也可以配置为,当构建完成时发送电子邮件通知。笔者建议在构建失败时将电子邮件发送给整个团队,以便可以尽快修复。
警告
一次失败的构建并不是奇耻大辱。每个人都会犯错,开发人员也不能幸免。当构建失败时,我们应当将其视为一个受欢迎的结果,因为该问题被及早地发现了。尽早失败并且尽早修复问题是CI的关键目标。
CI不仅仅针对开发人员。通过安装扩展,我们可以从CI系统导出各种指标,它们不仅可以用于提高软件质量,还可以提高开发实践的质量。
自动部署
“大多数CI系统允许在构建完成后运行一些脚本。在大多数情况下,我们可以编写脚本将应用程序部署到每个人都可以查看的一台在线测试服务器。这种思维模式的进一步演变便是持续部署,它需要将软件直接部署到生产环境里,这往往需要额外的自动化手段来防止缺陷或被还原。”
警告
并非所有项目都需要自动部署,尤其是当企业服务器运行在客户站点。项目计划决定了客户站点升级到最新版本的时间,这通常是几个月前就计划好的。如果生产站点是同样是由正在开发该软件的公司自主托管的话,那么对于持续部署系统的投入将更有收益。持续部署是持续集成流程到位并且运转良好时后续的逻辑步骤。
并非所有的提交都能够产出一个可交付的产品。敏捷社区中最常见的误解是认为每个版本都是可交付的产品。可交付的产品与能正常工作的软件的定义完全不同!
笔者希望这些信息可以让用户深入了解一些改进CI流程实施的最佳做法。CI在简化软件开发过程中发挥着重要作用。CI实践的适当调整将提高软件开发过程的整体效率和灵活性。结合这些最佳实践是以更快的上市时间交付高品质软件的诀窍!