持续集成:软件质量改进和风险降低之道
主旨
这本书讲的是关于持续集成的原则和实践。Martin Fowler关于CI的热门文章发表于2006年,这本书作于2007年,虽然十年间CI的工具已经发生了不少变迁,但本书中提到的基本原则和实践仍然值得借鉴,而且书中提到的关于CI未来发展方向的论述也得到了验证。
本书分为两部分:
- 第1部分:CI的背景知识,包括基本概念、基本原则与推荐的实践
- 第2部分:如何创建全功能的CI系统,包括五个持续:
- 持续数据库集成
- 持续测试
- 持续审查
- 持续部署
- 持续反馈
第1部分 CI的背景知识
什么是持续集成
Martin Fowler在其文章中将CI描述为:
一种软件开发实践,即团队的成员经常集成他们的工作,通常每个成员每天至少集成一次——这导致每天发生多次集成。每次集成都通过自动化的构建(包括测试)来验证,从而尽快地检测出集成错误。许多团队发现,这个过程会大大减少集成问题,让团队能够更快地开发出一致的软件。
这意味着持续集成其实是一系列实践的集合,例如:
- 所有开发者均先执行个人构建,在将代码提交到代码库中,以保证集成构建不会失败
- 开发者每天至少向代码库提交一次代码
- 集成构建每天会在一台独立的机器上执行多次
- 每次构建必须100%通过测试
- 构建必须有产物,如可部署的包等
- 修复失败的构建是最高优先级的事情
- 构建中包含代码复查,生成如代码质量报告或依赖分析报告等,作为改进的参考
关于CI:
- CI实践中提倡自动化,因为集成本身就是一个需要多次重复的过程,自动化有利于降低出错的可能性
- 使用CI的一个明显好处是能够得到快速反馈,这与重构、TDD等实践的理念是一致的,即进行小的变更,而CI为这些变更提供了一张安全网
- 关于“持续”一词的解读,其实更应理解为“经常”集成
引入持续集成
- 越早引入CI越好。因为越到项目晚期实现CI会越困难,人们迫于压力有可能会拒绝改变。如果不得不在项目晚期开始实现CI,建议先从较小的工作开始,逐渐扩大范围
- 应该“早集成,常集成”。因此应该经常提交代码,更多的提交通常意味着更小的变更,风险会更小
- 项目文化中应该将修复失败的构建为最高优先级的事,否则团队中的人就无法继续自己的工作
利用CI减少风险
CI不仅仅能帮你节省时间,更能帮你减少项目中的以下风险:
没有可部署的软件
出现这种风险的原因可能有:
- 没有使用独立的机器来负责软件集成
- 没有将数据库变更通过版本化的脚本管理起来
- 手工部署软件导致的错误
很晚才发现缺陷
出现这种风险的原因可能有:
- 没有实现自动化的回归测试,仅凭手工没有时间回归所有测试
- 较低的测试覆盖率
缺少项目可见性
出现这种风险的原因可能有:
- 人工沟通导致信息的丢失与理解偏差
- 缺乏对软件系统架构或类图的可视化呈现
低品质的软件
出现这种风险的原因可能有:
- 没有遵守编码标准的代码
- 没有遵守架构标准的设计
- 重复的代码
第2部分 如何创建全功能的CI系统
持续数据库集成
持续数据库集成(Continuous Database Integration,CDBI)指的是将数据库的变更视为集成的一部分,在每次项目的版本库发生变更时,重建数据库和测试数据库。
为什么要进行数据库集成自动化?
因为团队没有赋予个人修改数据库的能力,许多项目中DBA经常成为瓶颈。
持续数据库集成的推荐实践
- 将创建数据库、删除数据库、插入数据等任务使用脚本自动化,让每个开发者都能够使用简单如
db:create
式的命令执行 - 使用不同的SQL文件来支持不同的环境,如开发、QA、生产环境等
- 每个开发者应该有独享的数据库“沙盒”来隔离代码变更,比如独立的schema,或者独立的数据库
- 将DBA解放出来研究和完成一些更高级的任务,比如优化数据库的性能、优化SQL的性能、数据规范化等
- 数据库也有测试工具,如PL/Unit、QUnit、SQLUnit等
持续测试
为什么要进行持续测试?
根据系统工程的可靠性定理,线性系统的可靠性是每个系统组件可靠性的乘积,这意味着系统整体的可靠性要低于任一个系统组件的可靠性。更何况考虑到系统组件之间的连接,软件系统应该是一个非线性系统,可靠性比线性系统更低,所以我们必须测试每一个系统组件的可靠性。
自动化测试的分类
- 单元测试:对系统中最小的代码单元的测试,这个单元通常是一个类
- 组件测试:验证系统的各个部分组合到一起能够产生预期的行为,通常通过API来执行
- 系统测试:验证整个系统的行为是否正确,通常通过外部接口,如web界面或GUI等来进行,可以通过Selenium这样的框架来驱动浏览器执行
- 功能测试:从客户的角度来验收整个应用程序是否满足所需功能,通过模拟用户行为来执行,通常也被认为是用户验收测试
持续测试的推荐实践
- 将不同的开发者测试分类
- 将自动化的开发者测试提交到代码库中
- 先执行较快的测试
- 使用适当的框架,让组件测试可重复
持续审查
持续审查是指对代码风格、代码是否符合编码标准的检查,需要通过自动化代码审查和人工审查相结合来进行。
自动化代码审查是人的智慧的增强,通过诸如PMD、Simian等工具来检查代码的圈复杂度、耦合度等指标,有的还能提示代码中潜在的风险。
圈复杂度(Cyclomatic complexity)是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出。
在软件测试的概念里,圈复杂度用来衡量一个模块判定结构的复杂程度,数量上表现为线性无关的路径条数,即合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。
|
|
但自动化代码审查并不能检出所偶问题,人工审查仍是必不可少的,人工审查更关注于自动化代码审查检查不出来的东西,比如代码的可读性、可维护性等。
持续部署
持续部署的目的是让团队随时随地都可以按需发布能工作的软件,同时使得发布工作量最小。
部署工作应该简化到只需一条类似ant deploy
式的命令即可完成。
一个典型的部署工作由6大步骤组成:
- 为库中的资产打上标签:为版本库中的同一组文件打上标签,表示是与同一个包有关。相当于为包依赖的代码文件做一组快照。
- 得到干净的环境:在作者写这本书时docker还没有诞生,所以要得到一个干净的环境,通常需要删除所有已安装的软件甚至重新安装操作系统,所幸的是今天已有容器技术帮助实现应用程序环境之间的隔离,我们可以随时从一个干净的操作系统开始,层层构建所需的依赖。
- 为构建版打上标签:这里的构建版标签与第1步中的版本库标签是不同的。版本库标签用来说明一组文件是相关的,是同属于一个版本的;构建版标签用于表示构建出的二进制包是唯一的。
- 执行所有的测试:部署前的测试重点是在一个干净的类生产环境下执行所有的测试,包括所有的自动化测试,以及人工检查。尽管自动化测试能够覆盖许多检查项,但人工检查仍是必不可少的,因为软件仍然是由人来使用的产品,人工检查能够检查出一些自动化测试测不出来的问题,比如UI界面方面的问题等。
- 创建构建反馈报告:生成本次构建的报告,包括文件变化信息、修复了哪些缺陷、实现了哪些功能等,在团队中共享。
- 回滚的能力:如果部署后发现问题,通过构建版标签和版本库标签可以找到想要的版本,快速回滚到前一个构建版,“撤销”部署。
持续反馈
CI存在的意义就是为了能够快速构建并让构建快速失败,目的就是为了能够获得快速的反馈信息。
持续反馈就是在正确的时间,以正确的方式,将正确的信息发送给正确的人。
CI系统可以利用的反馈机制有邮件、SMS、可视设备、windows任务条、宽屏显示器等。时至今日,还有IM消息这样的方式可供选择。
CI的未来
在写作这本书时,人们在CI的实践中经常抱怨:
- 怎样才能防止失败的构建频繁发生?
- 怎样才能让构建执行得更快?
作者期待在将来看到更多的工具能够支持以下功能:
- 开发者在合入代码前能够先在一台独立的计算机上执行集成构建,即个人构建,成功后才合入代码
- 并行化方面提供更多功能,或者利用额外的硬件和软件资源来加速构建