当然,别指望测试就是万能的。常言道,测试并不代表就没有bug。
每人每天都向主线提交代码
集成首先在于交流,它使其他成员能够看到你所做的修改。在这种频繁的交流下,大家都能很快地知道开发过程中所做的修改。
在向主线提交代码之前,开发人员必须保证本地构建成功。这当然也包括使测试全部通过。另外,在提交之前需要更新本地代码以匹配主线代码,然后在本地解决主线代码与本地代码之间的冲突,再在本地进行构建。如果构建成功,便可以向主线提交代码了。
在这种频繁提交下,开发者可以快速地发现自己代码与他人代码之间的冲突。快速解决问题的关键在于快速地发现问题。几个小时的提交间隔使得代码冲突也可以在几个小时内发现,此时大家的修改都不多,冲突也不大,因此解决冲突也很简单。对于好几周都发现不了的冲突,通常是很难解决的。
在更新本地代码库时就进行构建,这意味着我们既可以发现文本上的冲突,又可以发现编译冲突。既然构建是自测试的,那么运行时的冲突也可以被检测出来,而这样的冲突往往是一些特别烦人的bug。由于提交间隔只有短短的几个小时,bug便没多少藏身之处了。再者,因为每次提交的修改都不多,你可以使用diff-debugging来帮你找出这些bug。
我的基本原则是:每个开发者每天都应当向代码库进行提交。在实践中,越是频繁提交,可能导致冲突的地方就越少,因而也越容易发现。
频繁提交鼓励开发人员以几个小时为单位来分割他们的代码,这样便于跟踪进度。通常,人们一开始认为在短短的几个小时内做不了什么事情,但我们发现找个导师和多实践可以帮助他们学习。
每次提交都应在集成机上进行构建
有了每日提交,也就又了每日测试,这应该表明主线处于健康状态。但是在实践中,的确有出错的时候,原因之一在于纪律——有人并没有在提交之前进行本地更新和构建。另外,不同开发机之间的环境不同也是一个原因。
因此,你应该保证在集成机上进行构建,只有当集成机上构建成功后,才表明你的任务完成了。由于提交者需要对自己的提交负责,他就得盯着主线上的构建,如果失败,马上修改。如果下班之前你提交的修改失败了,那么,对不起,请修改好了才回家。
我见到过两种方式来保证主线构建的成功:一是手动构建,二是使用持续集成服务器。
手动构建是最简单的,基本上与开发者在本地做的构建差不多——先到集成机上拉下主线的最新代码,然后运行构建命令,在构建过程中你得盯着构建过程,如果构建成功,表明你的任务完成。(另见Jim Shore的描述。)
持续集成服务器则一直监视着代码库,一旦检测到有提交,便自动拉下代码到本机,然后开始构建,并将结果通知提交者。只有当提交者收到通知后——通常是以电子邮件的方式,才表明自己的任务完成。
在ThoughtWorks,我们是持续集成服务器的忠实粉丝,我们领导了CruiseControl和CruiseControl.NET的初期开发,此两者均是广为使用的CI服务器。从那时起,我们也开发了商业化的Cruise。在几乎每个项目中,我们都使用了CI服务器,并且结果是令人愉悦的。
不是所有人都倾向于使用CI服务器的,Jim Shore便给出了一个很好的论述,在此论述中,他解释了为什么他更倾向于手动构建。我同意他的看法——CI不过是安装一些软件而已,所有的实践都应当旨在有效地完成持续集成。但同样,许多使用CI服务器的团队的确发现CI服务器是很好的工具。
有很多团队定期的进行构建,比如每晚构建。这和持续构建并不是一回事,而且对于持续集成来说,也是不够的。持续集成的关键在于尽快地发现问题。而每晚构建意味着整个白天都发现不了bug,如此,需要很长的时间发现并清楚这些bug。
持续构建的重点在于,如果主线构建失败,你应该马上进行修改。在持续集成中,你一直是在一个稳定的代码库基础上进行开发。主线构建失败并不是一件坏事,但是,如果这样的情况经常发生,那么就意味着开发人员对于本地更新并没在意或者在提交之前并没在本地构建。主线构建一旦失败,必须马上修正。为了避免主线构建失败,也许你可以试试 pending head。
快速构建
持续集成的关键在于快速反馈,需要长时间构建的CI是极其糟糕的。我的多数同事都认为一个小时的构建时间对于CI来说决无道理可言。我也记得曾经有团队梦想着他们的构建能有多么多么的快,但有时我们不得不面对很难快速构建的情况。
对于多数项目来说,将构建时间维持在10钟之内是合理的,这也是XP的方针之一,我们多数项目也达到了这个目标。这种做法是值得的,因为这样省下的时间是为开发者节约的。
如果你的构建长到了一小时,那么想使其加速便不是那么容易了。对于企业级应用来说,我们发现构建时间的瓶颈通常发生在测试上,特别是那些需要于外部交互的测试——比如数据库。
可能最好的解决办法是引入阶段性构建(也叫构建管道或者部署管道),因为构建事实上是分阶段性的。代码提交后首先触发的是构建称为提交构建,提交构建应该快速完成,而棘手的是怎么保持速度与查找bug之间的平衡。
提交构建成功后,其他人便可自信的工作了。但是,你可能还有其它跑得比较慢的测试需要写,这时可以用额外的机器来专门跑这些耗时的测试。
一个简单的例子是将构建分为两个阶段,第一个阶段完成编译,并且跑那些不需要外部交互的单元测试,数据库交互也通过stub的方式完全消除掉。这些测试可以很快跑完,原则是将其保持在10分钟之内。但是,对于那些需要大量外部交互——特别是涉及到真实数据库交互时才能发现的bug,这个阶段便无能为力了。第二个阶段跑的测试则需要操作真实的数据库了,同时还应包括端到端测试。这个阶段可能需要几个小时。
在这种情况下,通常将第一阶段视为提交构建,并将此做为主要的CI周期。第二阶段则可在有必要时才进行,如果这个阶段构建失败,它也不需要像第一阶段那样“停下全部手头的工作”,但也应该得到尽快的修改。第二阶段的构建不见得需要保持一直通过,对于已经发现的bug来说,可以在之后几天修改。对于这个案例来说,第二阶段全是测试,因为通常情况下最慢的即是测试。