本文翻译自:https://martinfowler.com/articles/continuousIntegration.html
持续集成
持续集成是一种软件开发实践,团队成员经常整合他们的工作,通常每个人至少每天集成一次 - 每天导致多个集成。 每个集成都通过自动构建(包括测试)进行验证,以尽快检测集成错误。 许多团队发现这种方法可以显着减少集成问题,并允许团队更快地开发内聚软件。 本文简要概述了持续集成,总结了该技术及其当前用法。
我清楚地记得我第一次看到一个大型软件项目。 我正在一家大型英国电子公司做暑期实习。 我的经理,质量保证小组的一部分,让我参观了一个网站,我们进入了一个堆满了立方体的巨大压抑仓库。 有人告诉我,这个项目已经开发了几年,目前正在整合,并已整合了几个月。 我的向导告诉我,没有人真正知道完成整合需要多长时间。 从中我学到了一个软件项目的常见故事:集成是一个漫长而不可预测的过程。
但这不一定是这样。 我在ThoughtWorks的同事以及世界上许多其他人完成的大多数项目都将集成视为非事件。 任何单个开发人员的工作距离共享项目状态只有几个小时,并且可以在几分钟内集成回该状态。 可以快速找到任何集成错误,并且可以快速修复。
这种对比不是昂贵而复杂的工具的结果。 它的本质在于团队中每个人经常(通常是每天)与受控源代码库集成的简单实践。
当我向人们描述这种做法时,我经常会发现两种反应:“它不能起作用(这里)”和“做它不会产生太大的影响”。人们在尝试时发现的是它比听起来容易得多,并且它对开发产生了巨大的影响。因此,第三个常见的反应是“是的,我们这样做 - 你怎么能没有它?”
“持续集成”一词源于极限编程开发过程,是其最初的十二种实践之一。当我在ThoughtWorks开始时,作为顾问,我鼓励我正在一起工作的项目组使用持续集成。 Matthew Foemmel将我模糊的劝诫变成了坚实的行动,我们看到这个项目从罕见和复杂的整合到我所描述的非事件。马修和我在本文的原始版本中写下了我们的经验,这是我网站上最受欢迎的文章之一。
尽管持续集成是一种不需要特定工具进行部署的实践,但我们发现使用Continuous Integration服务很有用。最着名的服务是CruiseControl,这是一个开源工具,最初由ThoughtWorks的几个人构建,现在由一个广泛的社区维护。从那时起,其他几个CI服务器已经出现,包括开源和商业 - 包括来自ThoughtWorks Studios的Cruise。
通过持续集成构建特征
解释CI是什么以及它如何工作的最简单方法是展示一个如何使用小功能开发的快速示例。 让我们假设我必须对一个软件做一些事情,任务是什么并不重要,目前我认为它很小,可以在几个小时内完成。 (我们将在稍后探讨更长的任务和其他问题。)
我首先将当前集成源码的副本复制到我的本地开发机器上。 我通过使用源代码管理系统检查主线上的工作副本来实现此目的。
上面的段落对使用源代码控制系统的人有意义,但对那些不使用源码控制系统的人来说是胡言乱语。 所以让我快速解释一下后者。 源代码控制系统将项目的所有源代码保存在存储库中。 系统的当前状态通常被称为“主线”。 在任何时候,开发人员都可以将主线的受控副本放到他们自己的机器上,这称为“检出”。 开发人员机器上的副本称为“工作副本”。 (大多数情况下,您实际将工作副本更新到主线 - 实际上它也是一样的。)
现在我拿起我的工作副本,做我需要做的任何事情来完成我的任务。 这将包括更改生产代码,以及添加或更改自动化测试。 持续集成假设高度自动化到软件中的测试:我称之为自测代码的工具。 通常这些使用流行的XUnit测试框架的版本。
一旦我完成了(通常在我工作时的各个阶段),我在我的开发机器上进行自动构建。 这将获取我的工作副本中的源代码,编译并将其链接到可执行文件中,并运行自动化测试。 只有在所有构建和测试没有错误的情况下,整体构建才被认为是好的。
通过良好的构建,我可以考虑将我的更改提交到存储库中。 当然,在我有机会投入之前,其他人可能并且通常已经对主线进行了更改。 所以首先我用他们的更改更新我的工作副本并重建。 如果他们的更改与我的更改发生冲突,它将在编译或测试中显示为失败。 在这种情况下,我有责任修复此问题并重复,直到我可以构建与主线正确同步的工作副本。
一旦我自己构建了一个正确同步的工作副本,我就可以最终将我的更改提交到主线,然后主线更新存储库。
但是我的提交没有完成我的工作。 此时我们再次构建,但这次是在基于主线代码的集成机器上。 只有当这个构建成功时,我们才能说我的更改已经完成。 我总是有机会错过我的机器上的东西,并且存储库没有正确更新。 只有当我提交的更改在集成上成功构建时,我才能完成工作。 这个集成版本可以由我手动执行,也可以由Cruise自动完成。
如果两个开发人员之间发生冲突,则通常会在第二个提交的开发人员构建其更新的工作副本时捕获。 如果不是,集成构建应该失败。 无论哪种方式,都能快速检测到错误。 此时,最重要的任务是修复它,并使构建再次正常工作。 在持续集成环境中,您永远不应该有一个失败的集成构建保持失败很长时间。 一个好的团队每天应该有很多正确的构建。 不良版本确实会不时发生,但应该快速修复。
这样做的结果是有一个稳定的软件可以正常工作并且包含很少的错误。 每个人都发展出共享稳定的基础,并且永远不会离这个基地太远,以至于需要很长时间才能与它集成。 尝试查找错误所花费的时间更少,因为它们很快就会出现。
小结:
1 | 上文讲了持续集成的一个完整过程: |
持续集成的实践
上面的故事是CI概述及其在日常生活中的运作方式。 让这一切顺利进行显然不止于此。 我现在将重点关注构成有效CI的关键实践。
维护单一源码库
软件项目涉及许多需要一起编排以构建产品的文件。 跟踪所有这些是一项重大努力,特别是当涉及多个人时。 因此,多年来软件开发团队已经构建了管理所有这些的工具,这并不奇怪。 这些工具 - 称为源代码管理工具,配置管理,版本控制系统,存储库或其他各种名称 - 是大多数开发项目不可或缺的一部分。 令人遗憾和令人惊讶的是,他们不是所有项目的一部分。 这种情况很少见,但我确实遇到了不使用这种系统的项目,并使用了本地和共享驱动器的混乱组合。
因此,作为一个简单的基础,请确保您获得一个体面的源代码管理系统。 由于可以获得高质量的开源工具,因此成本不是问题。 当前的开源存储库是Subversion(现在应该是Git)。 (旧的开源工具CVS仍然被广泛使用,并且比没有好,但Subversion是现代的选择。)有趣的是,当我与开发人员交谈时,我知道大多数商业源代码管理工具比Subversion更受欢迎。 我一直听到人们说的唯一值得付出的工具是Perforce。
获得源代码管理系统后,请确保它是众所周知的获取源代码的地方。 没有人应该问“foo-whiffle文件在哪里?” 一切都应该在存储库中。
虽然许多团队使用存储库,但我看到的一个常见错误是他们没有将所有内容都放在存储库中。 如果人们使用其中一个,他们会将代码放在那里,但是你需要做的一切都包括:测试脚本,属性文件,数据库模式,安装脚本和第三方库。 我已经知道将他们的编译器检查到存储库的项目(在C++编译器的早期很重要)。 基本的经验法则是,你应该能够使用原始机器走向项目,进行检出,并能够完全构建系统。 原始机器上应该只有极少量的东西 - 通常是大而且安装复杂且稳定的东西。 操作系统,Java开发环境或基础数据库系统是典型示例。
您必须将构建所需的所有内容放在源代码管理系统中,但是您也可以将人们通常使用的其他内容放在那里。 IDE配置很适合放在那里,因为这样人们可以轻松共享相同的IDE设置。
版本控制系统的一个特性是它们允许您创建多个分支,以处理不同的开发流。这是一个有用的,非必要的功能 - 但它经常被过度使用并让人们陷入困境。尽量减少对分支机构的使用。特别是有一条主线:目前正在开发的项目的一个分支。几乎每个人都应该在大部分时间都在这条主线上工作。 (合理的分支是先前生产版本和临时实验的错误修复。)
通常,您应该在源代码控制中存储构建任何内容所需的所有内容,但实际上不构建任何内容。有些人确实将构建产品保留在源代码控制中,但我认为这是一种气味 - 表明更深层次的问题,通常无法可靠地重新构建。
自动化构建
将源码变为运行系统通常是一个复杂的过程,涉及编译,移动文件,将模式加载到数据库等等。 但是,与软件开发的这一部分中的大多数任务一样,它可以自动化 - 因此应该是自动化的。 要求人们键入奇怪的命令或点击对话框是浪费时间和错误的滋生地。
构建的自动化环境是系统的常见特征。 Unix世界已经有几十年了,Java社区开发了Ant,.NET社区已经拥有了Nant,现在已经拥有了MSBuild。 确保您可以使用单个命令使用这些脚本构建和启动系统。
一个常见的错误是不要在自动构建中包含所有内容。 构建应该包括从存储库中获取数据库模式并在执行环境中将其激活。 我将详细说明我之前的经验法则:任何人都应该能够引入原始计算机,检查存储库中的源代码,发出单个命令,并在其计算机上运行正在运行的系统。
构建脚本有各种各样的风格,通常特定于平台或社区,但它们并非必须如此。 虽然我们的大多数Java项目都使用Ant,但有些使用了Ruby(Ruby Rake系统是一个非常好的构建脚本工具)。 我们从使用Ant自动化早期Microsoft COM项目中获得了很多价值。
大型构建通常需要时间,如果您只进行了一些小改动,则不希望执行所有这些步骤。 因此,一个好的构建工具可以分析在过程中需要更改的内容。 执行此操作的常用方法是检查源文件和目标文件的日期,并仅在源日期较晚时进行编译。 然后依赖性变得棘手:如果一个对象文件发生了变化,那么依赖它的对象也可能需要重建。 编译器可以处理这种事情,或者他们可能不会。
根据您的需要,您可能需要构建不同类型的东西。 您可以使用或不使用测试代码或使用不同的测试集来构建系统。 某些组件可以独立构建。 构建脚本应该允许您为不同的情况构建替代目标。
我们中的许多人使用IDE,并且大多数IDE在其中都有某种构建管理过程。 但是,这些文件始终是IDE专有的,并且通常很脆弱。 此外,他们需要IDE才能工作。 IDE用户可以设置自己的项目文件并将其用于个人开发。 但是,拥有一个可在服务器上使用并可从其他脚本运行的主构建是至关重要的。 因此,在Java项目中,我们可以让开发人员在他们的IDE中构建,但是主构建使用Ant来确保它可以在开发服务器上运行。
让你的构建可以自测试
传统上,构建意味着编译,链接以及使程序执行所需的所有其他内容。 程序可能会运行,但这并不意味着它做正确的事情。 现代静态类型语言可以捕获许多错误,但依然存在更多漏洞。
更快速有效地捕获错误的好方法是在构建过程中包含自动化测试。 当然,测试并不完美,但它可以捕获很多错误 - 足以使用。 特别是极限编程(XP)和测试驱动开发(TDD)的兴起在推广自测代码方面做了大量工作,因此许多人已经看到了该技术的价值。
我工作的定期读者将知道我是TDD和XP的忠实粉丝,但是我想强调,这些方法都不是获得自测代码的好处所必需的。 这两种方法都是在编写使代码通过的代码之前编写测试的一点 - 在这种模式下,测试与探索系统的设计一样多,因为它们是关于错误捕获的。 这是一件好事,但对于持续集成而言,这并不是必需的,因为我们对自测代码的要求较低。 (虽然TDD是我制作自测代码的首选方式。)
对于自测代码,您需要一套自动化测试,可以检查大部分代码库中的错误。 测试需要能够从一个简单的命令开始,并进行自我检查。 运行测试套件的结果应指示是否有任何测试失败。 对于要进行自我测试的构建,测试失败会导致构建失败。
在过去几年中,TDD的兴起推广了XUnit系列开源工具,这些工具非常适合这种测试。 事实证明,XUnit工具在ThoughtWorks上对我们非常有价值,我总是建议人们使用它们。 这些由Kent Beck开创的工具使您可以轻松地建立完全自我测试的环境。
XUnit工具无疑是使代码自我测试的起点。 您还应该关注其他专注于更多端到端测试的工具,目前有很多这样的工具,包括FIT,Selenium,Sahi,Watir,FITnesse以及其他很多我不是 试图在这里全面列出。
当然,你不能指望测试找到一切。 正如经常说的那样:测试不能证明没有错误。 然而,完美并不是您获得自我测试版本的唯一回报点。 经常运行的不完美测试比完全没有编写的完美测试要好得多。
每个人每天都会提交代码到主线
集成主要是关于沟通。 集成允许开发人员告诉其他开发人员他们所做的更改。 频繁的沟通使人们可以随着变化的发展而快速了解。
开发人员提交主线的一个先决条件是他们可以正确地构建他们的代码。 当然,这包括传递构建测试。 与任何提交周期一样,开发人员首先更新其工作副本以匹配主线,解决与主线的任何冲突,然后在其本地计算机上构建。 如果构建通过,那么它们可以自由地提交到主线。
通过这样做,开发人员很快就会发现两个开发人员之间是否存在冲突。 快速解决问题的关键是快速找到问题。 开发人员每隔几个小时就会发生一次冲突,可以在发生冲突的几个小时内检测到冲突,此时发生的冲突并不多,而且很容易解决。 几周未被发现的冲突可能很难解决。
更新工作副本时构建的事实意味着您可以检测编译冲突以及文本冲突。 由于构建是自我测试,因此您还可以检测代码运行中的冲突。 后者的冲突是特别难以找到的错误,如果他们在代码中长时间未检测到。 由于提交之间只有几个小时的更改,因此只有很多地方可能会隐藏问题。 此外,由于没有太多变化,您可以使用diff-debugging来帮助您找到错误。
我的一般经验法则是每个开发人员每天都应该提交到存储库。 在实践中,如果开发人员更频繁地提交,那么它通常很有用。 您提交的频率越高,您查找冲突错误的位置就越少,您解决冲突的速度就越快。
频繁的提交鼓励开发人员将他们的工作分解成几小时的小块。 这有助于跟踪进度并提供进步感。 人们通常认为他们在短短几个小时内就无法做出有意义的事情,但我们发现指导和实践有助于他们学习。
每个提交都应该在集成机器上构建主线
使用每日提交,团队可以获得经常测试的构建。 这应该意味着主线保持健康状态。 然而,在实践中,事情仍然出错。 一个原因是纪律,人们在提交之前没有进行更新和构建。 另一个是开发人员机器之间的环境差异。
因此,您应该确保在集成计算机上进行常规构建,并且只有在此集成构建成功时才应该认为提交已完成。 由于提交的开发人员对此负责,因此开发人员需要监视主线构建,以便在中断时能够修复它。 这样做的必然结果是,在主线构建已经过去并且您在当天晚些时候添加的任何提交之前,您不应该回家。
我见过两种主要方法来确保这一点:使用手动构建或持续集成服务器。
手动构建方法是最简单的描述方法。 从本质上讲,它与开发人员在提交到存储库之前所做的本地构建类似。 开发人员进入集成机器,检查主线的头部(现在是他的最后一次提交)并启动集成构建。 他一直关注着它的进步,如果构建成功,他就完成了他的提交。 (另见Jim Shore的描述。)
持续集成服务器充当存储库的监视器。 每次针对存储库的提交完成时,服务器都会自动将源检出到集成机器上,启动构建,并通知提交者构建结果。 在收到通知之前,提交者不会完成 - 通常是电子邮件。
在ThoughtWorks,我们是持续集成服务器的忠实粉丝 - 事实上,我们领导了CruiseControl和CruiseControl.NET的原始开发,后者是广泛使用的开源CI服务器。从那时起,我们还构建了商业Cruise CI服务器。我们几乎在每个项目中使用CI服务器,并且对结果非常满意。
并非所有人都喜欢使用CI服务器。 Jim Shore对他为什么更喜欢手动方法做了很好的描述。我同意他的看法,CI不仅仅是安装一些软件。这里的所有实践都需要有效地进行持续集成。但同样多的CI团队发现CI服务器是一个有用的工具。
许多组织按照定时计划定期构建,例如每晚。这与连续集成不同,并不足以进行持续集成。持续整合的重点是尽快发现问题。每夜构建意味着在任何人发现它们之前一整天都没有发现错误。一旦他们在系统中长时间,找到并删除它们需要很长时间。
立即修复破碎的构建
进行连续构建的关键部分是,如果主线构建失败,则需要立即修复。 与CI合作的全部意义在于,您始终在一个已知的稳定基础上发展。 主线构建要打破并不是坏事,尽管如果它一直在发生,它表明人们在提交之前没有足够小心地更新和构建本地。 但是,当主线构建确实中断时,快速修复它很重要。
我记得Kent Beck使用的一句话是“没有人比修复构建有更高优先级的任务”。 这并不意味着团队中的每个人都必须停止他们正在做的事情来修复构建,通常只需要几个人来让事情再次发挥作用。 它确实意味着有意识地将构建修复优先级化为紧急的高优先级任务。
通常,修复构建的最快方法是从主线恢复最新的提交,将系统恢复到最后已知的良好构建。 当然团队不应该尝试在破坏的主线上进行任何调试。 除非破损的原因立即显而易见,否则只需恢复主线并在开发工作站上调试问题。
为了避免破坏主线,您可以考虑使用待处理头。
当团队介绍CI时,通常这是最难解决的问题之一。 在团队的早期阶段,很难养成使用主线构建的常规习惯,特别是如果他们正在处理现有的代码库。 耐心和稳定的应用似乎经常做到这一点,所以不要气馁。
保持快速构建
持续集成的重点是提供快速反馈。 没有什么比一个需要很长时间的构建更能吸取CI活动血液的了。 在这里,我必须承认一个狡猾的老家伙认为长期构建是娱乐的。 我的大多数同事认为构建需要一个小时才是完全不合理的。 我记得团队梦想着他们可以如此快速地获得它 - 偶尔我们仍会遇到很难让构建达到这种速度的情况。
然而,对于大多数项目而言,10分钟构建的XP准则完全是合理的。 我们的大多数现代项目实现了这一点 值得集中精力实现这一目标,因为每次开发人员每次提交时,每一分钟都会为每个开发人员节省一分钟。 由于CI需要频繁提交,因此需要花费大量时间。
如果你正盯着一个小时的构建时间,那么进入更快的构建可能看起来是一个令人生畏的前景。在一个新项目上工作并考虑如何保持快速,这甚至是令人生畏的。至少对于企业应用程序,我们发现通常的瓶颈是测试 - 特别是涉及外部服务(如数据库)的测试。
可能最关键的一步是开始设置部署管道。部署管道(也称为构建管道或分阶段构建)背后的想法是实际上有多个构建按顺序完成。对主线的提交触发了第一次构建 - 我称之为提交构建。提交构建是当有人提交到主线时所需的构建。提交构建是必须快速完成的构建,因此它将需要许多快捷方式,这将降低检测错误的能力。诀窍是平衡错误查找和速度的需求,以便良好的提交构建足够稳定,以便其他人可以工作。
一旦提交构建良好,那么其他人可以放心地处理代码。 但是,您可以开始进行更久,更慢的测试。 其他计算机可以在构建上运行更长时间的测试例程。
一个简单的例子是两阶段部署管道。 第一阶段将进行编译和运行测试,这些测试是更加本地化的单元测试,数据库完全被删除。 这样的测试可以非常快速地运行,保持在十分钟的指导范围内。 但是,任何涉及更大规模交互的错误,特别是那些涉及真实数据库的错误都将无法找到。 第二阶段构建运行一组不同的测试,这些测试确实击中了真实的数据库,并涉及更多的端到端行为。 这个套件可能需要几个小时才能运行。
在这种情况下,人们使用第一阶段作为提交构建,并将其用作主要CI周期。第二阶段构建可以运行,从最新的良好提交构建中获取可执行文件以进行进一步测试。如果这个辅助构建失败,那么这可能没有相同的“停止一切”质量,但团队确实旨在尽可能快地修复此类错误,同时保持提交构建运行。在这个例子中,后来的构建通常是纯粹的测试,因为这些时间通常是导致缓慢的测试。
如果辅助构建检测到错误,则表明提交构建可以执行另一个测试。您希望尽可能确保任何后续阶段的失败都会导致提交构建中的新测试,这些测试会捕获该错误,因此该错误在提交构建中保持不变。这样,只要有东西超过它们,提交测试就会得到加强。在某些情况下,无法构建暴露错误的快速运行测试,因此您可能决定仅在辅助版本中测试该条件。大多数时候,幸运的是,您可以为提交构建添加合适的测试。
此示例属于两阶段管道,但基本原则可以扩展到任意数量的后续阶段。提交构建之后的构建也可以并行完成,因此如果您有两个小时的辅助测试,则可以通过让两台计算机分别运行一半测试来提高响应能力。通过使用这样的并行辅助构建,您可以在常规构建过程中引入各种进一步的自动化测试,包括性能测试。
在生产环境的克隆中进行测试
测试的目的是在受控条件下清除系统在生产中会遇到的任何问题。 其中很大一部分是生产系统运行的环境。 如果您在不同的环境中进行测试,则每个差异都会导致测试中发生的情况不会在生产中发生。
因此,您希望将测试环境设置为尽可能模拟生产环境。 使用相同的数据库软件,使用相同版本的操作系统。 将生产环境中的所有适当库放入测试环境中,即使系统实际上并未使用它们。 使用相同的IP地址和端口,在同一硬件上运行它。
嗯,实际上有限制。 如果您正在编写桌面软件,那么使用所有不同人员运行的第三方软件来测试每个可能桌面的克隆是不切实际的。 类似地,一些生产环境的复制成本可能过高(尽管我经常通过不复制中等昂贵的环境而遇到虚假经济)。 尽管存在这些限制,您的目标仍然是尽可能多地复制生产环境,并了解您为测试和生产之间的每个差异所接受的风险。
如果你有一个非常简单的设置,没有很多尴尬的通信,你可能能够在一个模仿的环境中运行你的提交构建。 但是,通常需要使用双倍测试,因为系统响应缓慢或间歇性。 因此,通常会有一个非常人工的环境来进行速度提交测试,并使用生产克隆进行二次测试。
我注意到人们越来越有兴趣使用虚拟化来轻松地将测试环境放在一起。 可以使用虚拟化中包含的所有必要元素保存虚拟机。 然后安装最新的构建和运行测试相对简单。 此外,这可以允许您在一台计算机上运行多个测试,或者在一台计算机上模拟网络中的多台计算机。 随着虚拟化的性能损失减少,此选项越来越有意义。
让任何人都可以轻松获得最新的可执行文件
软件开发中最困难的部分之一是确保构建正确的软件。 我们发现很难提前确定你想要的东西并且是正确的; 人们发现更容易看到不太正确的事情,并说明需要如何改变。 敏捷开发过程明确地期望并利用这部分人类行为。
为了帮助完成这项工作,任何参与软件项目的人都应该能够获得最新的可执行文件并能够运行它:用于演示,探索性测试,或者只是看看本周发生了什么变化。
这样做非常简单:确保有一个众所周知的地方,人们可以找到最新的可执行文件。 在这样的商店中放置几个可执行文件可能很有用。 对于最新的,您应该将最新的可执行文件传递给提交测试 - 如果提交套件相当强大,这样的可执行文件应该非常稳定。
如果您正在跟踪具有良好定义的迭代的过程,那么通常也可以将迭代构建的结束放在那里。 特别是演示需要熟悉其特性的软件,因此通常值得牺牲最新的演示者知道如何操作的东西。
每个人都可以看到发生了什么
持续集成完全是为了沟通,因此您需要确保每个人都能轻松查看系统状态以及对系统所做的更改。
沟通最重要的事情之一是主线构建的状态。如果您正在使用Cruise,那么会有一个内置的网站,它会告诉您是否有正在进行的构建以及最后一个主线构建的状态。许多团队喜欢通过将连续显示连接到构建系统来使这一点变得更加明显 - 构建工作时发出绿光的光或者如果失败则发红光是很受欢迎的。一种特别常见的触摸是红色和绿色熔岩灯 - 不仅仅是这些指示了构建的状态,而且还指示了该状态的持续时间。红色灯泡上的气泡表明构造已经被打破了太长时间。每个团队都会在这些构建传感器上做出自己的选择 - 玩你的选择很有趣(最近我看到有人正在试验一只跳舞的兔子。)
如果您使用的是手动CI流程,则此可见性仍然至关重要。 物理构建机器的监视器可以显示主线构建的状态。 通常你有一个构建令牌可以放在任何正在进行构建的人的桌面上(再次,像橡皮鸡一样愚蠢的东西是一个不错的选择)。 人们常常喜欢在良好的构造上发出简单的声音,比如敲响铃声。
当然,CI服务器的网页可以携带比这更多的信息。 Cruise不仅指出了谁在建造,还指出了他们所做的改变。 Cruise还提供了变更历史,使团队成员能够很好地了解项目的近期活动。 我知道团队负责人喜欢用它来了解人们一直在做什么,并了解系统的变化。
使用网站的另一个好处是,那些不在同一地点的人可以了解项目的状态。总的来说,我更愿意让每个人积极参与一个项目,但往往有外围人士喜欢关注事物。它还可以将组聚合在一起构建来自多个项目的信息 - 提供不同项目的简单自动状态。
良好的信息显示不仅仅是计算机屏幕上的信息。我最喜欢的一个展示是一个进入CI的项目。它有着无法建立稳定版本的悠久历史。我们在墙上放了一个日历,每天显示一个小方块的全年。如果QA小组收到一个通过提交测试的稳定版本,那么QA小组每天都会在当天贴上绿色标签,否则为红色方块。随着时间的推移,日历显示构建过程的状态显示稳定的改善,直到绿色方块如此常见以至于日历消失 - 其目的得以实现。
自动化部署
要进行持续集成,您需要多个环境,一个用于运行提交测试,一个或多个用于运行二级测试。 由于您每天多次在这些环境之间移动可执行文件,因此您需要自动执行此操作。 因此,拥有允许您轻松地将应用程序部署到任何环境的脚本非常重要。
这样做的一个自然结果是您还应该拥有允许您以类似的方式部署到生产中的脚本。 您可能不会每天部署到生产中(虽然我遇到了可以执行的项目),但自动部署有助于加快流程并减少错误。 它也是一个便宜的选择,因为它只使用您用于部署到测试环境中的相同功能。
如果您将一个额外的自动化功能部署到生产中,您应该考虑自动回滚。不好的事情会不时发生,如果有褐色的物质碰到旋转的金属,那么能够迅速恢复到最后一个已知的良好状态是件好事。能够自动恢复还可以减少部署的紧张程度,鼓励人们更频繁地部署,从而快速地向用户提供新功能。 (Ruby on Rails社区开发了一个名为Capistrano的工具,它是执行此类工作的工具的一个很好的例子。)
在集群环境中,我看到滚动部署,新软件一次部署到一个节点,在几个小时内逐步替换应用程序。
我遇到的公共Web应用程序的一个特别有趣的变体是将试验版本部署到用户子集的想法。然后,团队会在决定是否将其部署到完整用户群之前查看试用版的使用方式。这允许您在提交最终选择之前测试新功能和用户界面。自动部署与良好的CI规则相结合,对于实现这一目标至关重要
持续集成的好处
总的来说,我认为持续集成的最大和最广泛的好处是降低风险。 我的思绪仍然浮现在我在第一段中提到的早期软件项目中。 在那里,他们最终(他们希望)有一个漫长的项目,但却没有真正了解它们在完成之前需要多长时间。
延迟整合的问题在于,很难预测需要多长时间才能完成,更糟糕的是,很难看出你在这个过程中走了多远。 结果是,你正在把自己置于一个项目最紧密的部分之一的完全盲点 - 即使你是一个罕见的情况,你还不晚。
持续集成完全解决了这个问题。没有长时间的整合,你完全消除了盲点。在任何时候,您都知道自己在哪里,哪些有效,哪些无效,以及您系统中存在的突出错误。
Bug - 这些是破坏信心和搞乱时间表和声誉的令人讨厌的东西。部署的软件中的bug会让用户生气。正在进行的工作中的bug妨碍了您,使得其他软件更难以正常工作。
Continuous Integrations不会消除bug,但它确实使它们更容易查找和删除。在这方面,它更像是自我测试代码。如果你引入一个bug并快速检测它,那么摆脱它会容易得多。由于您只更改了系统的一小部分,因此您无需深入了解。由于系统的那一点是你刚刚使用过的,它在你的记忆中是新的 - 再次使它更容易找到bug。您还可以使用diff调试 - 将系统的当前版本与之前没有错误的版本进行比较。
错误也是累积的。你拥有的错误越多,删除每个错误就越困难。这部分是因为你得到了错误交互,其中失败表现为多个错误的结果 - 使每个错误更难找到。这也是心理上的 - 当人们有很多错误时,人们找不到勇气并摆脱错误 - 这是一种实用程序员称之为破碎窗户综合症的现象。
因此,持续集成的项目往往会在生产和流程中显着减少错误。但是我应该强调,这种好处的程度与测试套件的好坏直接相关。您应该发现构建一个明显不同的测试套件并不困难。但是,通常情况下,在团队真正达到他们有可能达到的低级别错误之前需要一段时间。实现目标意味着不断努力并改进您的测试。
如果您持续集成,它将消除频繁部署的最大障碍之一。频繁部署很有价值,因为它可以让您的用户更快地获得新功能,更快地获得有关这些功能的反馈,并且通常在开发周期中变得更加协作。这有助于打破客户与开发之间的障碍 - 我认为这些障碍是成功开发软件的最大障碍。
引入持续集成
所以你想要尝试持续集成 - 你从哪里开始? 我在上面概述的全套实践为您提供了全部优势 - 但您无需从所有实践开始。
这里没有固定的配方 - 很大程度上取决于您的设置和团队的性质。 但是,我们已经学到了一些让事情顺利进行的事情。
最初的步骤之一是使构建自动化。 获取源代码控制所需的一切,以便您可以使用单个命令构建整个系统。 对于许多项目来说,这不是一项小任务 - 但它对任何其他事情都很重要。 最初,您可能只是偶尔按需构建,或者只是进行自动夜间构建。 虽然这些不是持续集成,但自动夜间构建是一个很好的步骤。
在构建中引入一些自动化测试。 尝试确定出现问题的主要区域,并通过自动化测试来揭露这些故障。 特别是在现有项目中,很难快速获得一套非常好的测试 - 构建测试需要时间。 你必须从某个地方开始 - 所有关于罗马建造计划的陈词滥调都适用。
尝试加快提交构建。 在几个小时的构建上持续集成总比没有好,但是要达到那个神奇的十分钟数字要好得多。 这通常需要在您的代码库上进行一些非常严格的手术,因为您打破了对系统缓慢部分的依赖性。
如果要开始新项目,请从头开始进行持续集成。 密切关注构建时间,并在开始慢于十分钟规则时立即采取措施。 通过快速行动,您将在代码库变得如此之大之前进行必要的重组,这将成为一个重大的痛苦。
最重要的是得到一些帮助。 找一个做过持续集成的人来帮助你。 像任何新技术一样,当你不知道最终结果是什么时,很难引入它。 获得导师可能需要花钱,但如果你不这样做,你也会付出浪费的时间和生产力。 (免责声明/广告 - 是的,我们ThoughtWorks在这方面做了一些咨询。毕竟我们已经犯了很多错误。)
最后的想法
自从Matt和我在本网站上撰写原始论文以来,持续集成已经成为软件开发的主流技术。 几乎没有任何ThoughtWorks项目没有它 - 我们看到其他人在世界各地使用CI。 我几乎没有听说过这种方法的负面影响 - 不像一些更有争议的极限编程实践。
如果您没有使用持续集成,我强烈建议您尝试一下。 如果你是,也许本文中有一些想法可以帮助你更有效地做到这一点。 我们在过去几年中学到了很多关于持续集成的知识,我希望还有更多需要学习和改进的地方。
进一步阅读
像这样的文章只能涵盖这么多,但这是一个重要的主题,所以我在我的网站上创建了一个指南页面,为您指出更多信息。
为了更详细地探索持续整合,我建议你看一下Paul Duvall关于这个主题的适当标题的书(赢得了Jolt奖 - 比我曾经管理过的更多)。 有关更广泛的持续交付流程的更多信息,请查看Jez Humble和Dave Farley的书 - 这本书也让我获得了Jolt奖。
您还可以在ThoughtWorks站点上找到有关持续集成的更多信息。
旁注
关于持续集成的原始文章描述了我们的经验,因为Matt帮助将2000年的ThoughtWorks项目持续集成。
Jez Humble和Dave Farley将这些想法扩展到持续交付的主题,更详细地介绍了部署管道的概念。 他们的书“持续交付”在2011年正确地赢得了Jolt卓越奖。
许多人担心如何处理频繁发布的数据库。 Pramod Sadalage和我写了这篇文章,解释了如何通过自动重构和数据库迁移来解决这个问题。