持续集成与持续交付实战:用Jenkins、Travis CI和CircleCI构建和发布大规模高质量软件

978-7-115-58472-4
作者: [美]让-马塞尔·贝尔蒙特(Jean-Marcel Belmont )
译者: 张成悟陈佳祺
编辑: 刘雅思

图书目录:

详情

本书是一本持续集成与持续交付(CI/CD)实践指南,全书共15章。书中首先介绍持续集成和持续交付的基础知识,并介绍Jenkins用户界面及其安装方式;接下来介绍使用Jenkins UI开发插件、构建Jenkins流水线和运行Docker集成的实际操作;最后介绍Travis CI和CircleCI的安装及脚本运行等,帮助读者通过Travis CI和CircleCI获得有关CI/CD的广泛知识。 本书适合系统管理员、DevOps工程师以及构建和发布工程师阅读。通过阅读本书,读者能了解CI/CD的概念,并获得使用CI/CD生态系统中重要工具的实践经验。

图书摘要

版权信息

书名:持续集成与持续交付实战:用Jenkins、Travis CI和CircleCI构建和发布大规模高质量软件

ISBN:978-7-115-58472-4

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美] 让-马塞尔•贝尔蒙特(Jean-Marcel Belmont)

译    张成悟 陈佳祺

责任编辑 刘雅思

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e58472”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


Copyright © Packt Publishing 2018. First published in the English language under the title Hands-On Continuous Integration and Delivery: Build and release quality software at scale with Jenkins, Travis CI, and CircleCI.

All Rights Reserved.

本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


本书是一本持续集成与持续交付(CI/CD)实践指南,全书共15章。书中首先介绍持续集成和持续交付的基础知识,并介绍Jenkins UI及其安装方式;接下来介绍使用Jenkins UI开发插件、构建Jenkins 2.0流水线和进行Docker集成的实际操作;最后介绍Travis CI和CircleCI的安装及脚本运行等,帮助读者通过Travis CI和CircleCI获得有关CI/CD的广泛知识。

本书适合系统管理员、DevOps工程师以及构建和发布工程师阅读。通过阅读本书,读者能了解CI/CD的概念,并获得使用CI/CD生态系统中重要工具的实践经验。

编写现代软件非常困难,因为软件交付涉及许多团队,包括开发、质量保证(quality assurance,QA)、运维、产品所有者、客户支持和销售等。在构建软件的过程中需要尽可能将软件开发自动化。持续集成(continuous integration,CI)和持续交付(continuous delivery,CD)的过程将有助于确保交付给最终客户的软件具有最高的质量,并且能通过CI/CD流水线(pipeline)中的一系列检查。在本书中,读者将学习如何使用Jenkins编写自由风格(freestyle)脚本、插件,以及如何使用新版本的Jenkins 2.0用户界面(user interface,UI)和流水线。读者将通过用户界面、Travis命令行界面(command-line interface,CLI)、高级日志和调试技术来了解Travis CI,并学习Travis CI的最佳实践;还将通过用户界面、Circle CLI、高级日志和调试技术来了解CircleCI,并学习CircleCI的最佳实践。本书还将讨论容器、安全性和部署等概念。

本书适合系统管理员、质量保证工程师、DevOps和站点可靠性工程师阅读。读者需要对Unix编程、基本编程概念和版本控制系统(如Git)有所了解。

第1章介绍自动化的概念,并与手动流程进行对比,说明自动化的重要性。

第2章介绍持续集成的概念,解释什么是软件构建并介绍CI构建实践。

第3章介绍持续交付的概念,并特别说明软件交付、配置管理、部署流水线和部署脚本编写的问题。

第4章通过解释沟通问题来介绍CI/CD的业务价值(如与团队成员沟通问题的能力、团队成员之间的责任分担、了解利益相关者等),并说明CI/CD的重要性。

第5章帮助读者在Windows、Linux和macOS等操作系统上安装Jenkins。读者还将学习如何在本地系统中运行Jenkins以及如何管理Jenkins。

第6章介绍如何在Jenkins中编写、配置自由风格脚本,以及如何在自由风格脚本中添加环境变量和调试。

第7章介绍软件中的插件、如何使用Java和Maven创建Jenkins插件,并介绍Jenkins插件生态系统。

第8章详细介绍Jenkins 2.0,提供Jenkins 2.0(Blue Ocean)的操作说明,并介绍新的流水线语法。

第9章介绍Travis CI,解释Travis CI与Jenkins的区别,介绍Travis生命周期和Travis YML语法,并说明如何入门和设置GitHub。

第10章介绍安装Travis CI CLI的方法,详细解释CLI中的每条命令,介绍如何在Travis CI中将任务自动化,以及如何使用Travis API。

第11章详细介绍Travis Web UI,并展示Travis CI中日志与调试的进阶技术。

第12章介绍使用GitHub和Bitbucket设置CircleCI的方法,展示如何导航CircleCI Web UI,介绍CircleCI YML语法。

第13章介绍安装CircleCI CLI的方法,解释CLI中的每条命令,介绍CircleCI的工作流以及CircleCI API的使用方法。

第14章详细介绍作业日志,展示如何在CircleCI中调试慢速构建,介绍CircleCI中的日志记录和故障排除技术。

第15章介绍编写冒烟测试、单元测试、集成测试、系统测试、CI/CD中的验收测试的最佳实践,密码和机密管理的最佳实践,以及部署中的最佳实践。

为了充分利用本书,读者需要熟悉Unix编程概念(如使用Bash shell、环境变量和shell脚本)并了解Unix中的基本命令。读者应该熟悉版本控制的概念,知道提交的含义,并了解如何使用Git。读者还应该了解基本的编程语言,因为本书将使用Go、Node.js和Java等语言来构建CI / CD流水线和示例。

本书与操作系统无关,但是需要访问Unix环境和命令才能运用本书中的某些概念。因此,如果使用Windows操作系统,安装Git Bash和Ubuntu子系统可能会有帮助。读者需要在系统中安装Git、Docker、Node.js、Go和Java。文本编辑器(如Visual Studio Code)和终端控制台应用程序也有助于学习。


让-马塞尔•贝尔蒙特(Jean-Marcel Belmont)是一位对自动化和持续集成充满热情的软件工程师。他积极参与开源社区,经常参加各类不同主题的软件开发研讨会。他主持着多个开发小组,提倡整洁代码模式和软件匠艺。

我要感谢我充满爱心和耐心的妻子克里斯蒂娜、我的儿子迈克尔和我的女儿加布里埃拉在本书编写过程中给予我的支持、耐心和鼓励,同时也感谢我的朋友对我的鼓励。


哈伊•达姆(Hai Dam)在丹麦Netcompany公司担任DevOps工程师。他使用的DevOps工具链包括Jenkins、CircleCI、ELK、AWS和Docker。

克雷格• R• 韦伯斯特(Craig R Webster)为各种规模的客户开发过项目,包括小型创业公司(如Orkell、Picklive和Tee Genius)、Notonthehighstreet、英国政府和BBC等。凭借逾15年的开发和部署Web应用程序、交付流水线、自动化和托管平台的经验,克雷格能够胜任很多技术类型的项目,并确保按时、按预算交付。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

您还可以扫码右侧二维码, 关注【异步社区】微信公众号,回复“e58472”直接获取,同时可以获得异步社区15天VIP会员卡,近千本电子书免费畅读。

扫描下方二维码,您将会在异步社区微信服务号中看到本书信息及相关的服务提示。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书技术审校等工作,可以发邮件给本书的责任编辑(liuyasi@ptpress.com.cn)。

如果您来自学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接通过邮件发给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


在本书中,我们将研究持续集成(continuous integration,CI)和持续交付(continuous delivery,CD)的概念,并使用Jenkins、Travis CI和CircleCI等工具来实现它们。读者将动手编写许多脚本,并探索实际的CI/CD自动化脚本和方案。本章会虚构一个名为Billy Bob’s Machine Parts的公司,通过它来辅助阐释自动化的概念。Billy Bob’s Machine Parts公司有很多手动流程,且质量保证(quality assurance,QA)团队和开发团队之间的关系有些紧张,因为软件版本发布(release)仅由开发团队核心成员完成,并且所有QA测试都是手动完成的。

本章涵盖以下内容:

手动流程——讨论一种假设场景;

雇员的困境;

介绍自动化;

开发人员生产力;

打破沟通障碍;

创造合作环境。

本章将描述一个模拟的手动流程以及手动测试与手动流程中的固有缺陷,并说明使用CI/CD是如何极大地提高开发人员的生产效率的。在这个场景中,每个成员都设置了一组手动流程,而这些过程非常耗时。另外,如果软件的最新版本中的质量测试遇到问题,就必须重复运行这些步骤。

我们将研究该虚拟公司中多个团队的不同情况。

在某些情况下,开发团队、QA团队、客户成功团队和销售团队会遇到痛点。这里首先建立可能在这些团队中产生的业务场景,确定适合自动化的范围,并通过团队之间的交流来暴露出可以通过自动化大大提高效率的领域。

图1-1展示了一些业务场景。

图1-1

贝蒂·苏是Billy Bob’s Machine Parts公司QA团队的成员,这个公司有一个中等规模的开发团队。开发主管埃里克在季度末的周四上午开始手动进行版本发布,这需要花费他两天时间来完成,因为他是开发团队里唯一能够进行这项工作的人。埃里克在他的本地工作站上运行所有的测试,并在必要时集成紧急补丁。当埃里克完成后,他会把ZIP文件发送给QA团队的贝蒂。

贝蒂·苏所在的团队有两三位QA工程师,他们在周一上午开始最新版本的手动测试流程。贝蒂通知埃里克她在最新版本中发现了一些问题,并准备了一个Excel表格,记录最新版本引入的问题。在这周结束时,贝蒂将最新版本的问题分成了紧急、高、中、低优先级的程序错误(bug)。

 

软件错误(software bug)是指软件产品中产生了一个运行情况与预期不符的缺陷。

在一个版本的发布流程中,埃里克和贝蒂在处理问题时重复进行上述每一个步骤。埃里克需要重新打包所有的软件组件并在本地工作站上重新运行所有测试。贝蒂需要重新进行测试流程,进行软件回归测试(regression test)[1],并确保最新的修复不会破坏软件所有组件中已有的功能。

[1] regression,意为回归、退化,指软件模块由功能正常的状态退化到不正常的状态。——译者注

团队中的初级开发人员迈克尔也在进行手动流程。他从埃里克那里拿到问题清单,然后从列表中较高优先级的程序错误开始修复。迈克尔试图处理并修复每一个错误,但他未编写任何回归测试来确保新版本的代码没有破坏现有功能。当迈克尔完成工作后,他告诉埃里克他负责的部分情况良好,但是埃里克在自己的本地工作站运行测试时看到了测试错误。埃里克告知迈克尔,他在修复清单上的程序错误时应该更谨慎一些。

QA 团队的成员迪利恩开始测试新发行版本的各个部分,并告诉贝蒂这个版本有些问题。他创建了一个问题检查清单发送给贝蒂。然而,由于迪利恩与贝蒂在两份不同的检查清单里强调了相似的问题,他完成的一些工作与贝蒂的重复了。贝蒂告知迪利恩,QA人员需要确保没有重复的工作,于是迪利恩重新对他要测试的发行版本的各部分进行了强调。

珍妮弗是客户成功团队(customer success team)的主管,当新的发行版本准备好向客户发行时,她会收到QA团队的通知。然后珍妮弗开始准备关于最新版本功能的视频并询问QA团队关于新版本改动的问题。

鲍比是客户成功团队中一位经验丰富的成员,他开始针对最新功能制作视频。当发行版本的视频上传至公司博客后,QA团队发现一些视频错误地陈述了仍处于Beta版阶段的功能。珍妮弗迅速为客户成功团队澄清,并要求QA团队在将某些特点发送给客户成功团队之前将其明确标记为Beta版。

销售团队一直在通过电子邮件发送销售工程师与潜在客户会面时做的笔记。桑迪手动输入了关于每个潜在客户的详细记录,并使用Excel表格来分类重要销售信息。然而,销售团队将Excel表格的新变动通过电子邮件发送至本团队,如果一位销售工程师打开旧版本的Excel表格并错误地向其他销售工程师提供过时的信息,就会引发许多混乱。

用户界面与用户体验(User Interface/User eXperience,UI/UX)团队往往习惯使用大量的视觉稿(mockup)和线框图(wireframe)。在原型设计阶段,UI/UX团队经常在视觉稿中插入注释,并附有详细说明校验状态和页面交互的过渡注释。维克托询问UI/UX团队这些注释是否能与开发团队共享。UI/UX团队还使用画板工作,并为每个功能创建了ZIP文件。例如,桑迪被安排做关于功能X的工作,她对新页面的UI交互做了详细记录。UI/UX团队的许多工作是高度视觉化的,不同的颜色表示截然不同的内容,工作的视觉特征往往指UI流程阶段应该发生的动作。然而,开发人员倾向于处理更加具体的项目,对他们而言,什么自然流程应该发生并不总是显而易见的。例如,如果你要删除一个项目,你是打开一个模态窗口(询问是否确认删除的小窗口),还是立刻删除一个项目?在提交表格时,UI会以特定颜色展示错误提示并以另一个颜色展示警告吗验证应该放在什么位置?有时UI交互流程没有详细描述这些情况,开发人员必须与UI/UX团队来回沟通。在决策文件中记录决策的原因会变得非常重要。

贝蒂·苏通过电子邮件给维克托发送了按优先级分类的问题列表。较高优先级的问题必须先处理,而较低优先级的问题靠后处理。维克托收到最新版本的问题列表后,通知开发团队必须立刻停止正在进行的新功能开发工作,并开始修复最新版本的问题。团队中的高级开发人员大卫十分泄气,因为他正处在良好的工作节奏中,现在却被打断,不得不转向他一个月前做的工作。

迈克尔是团队中的初级开发人员,对代码库还不熟悉。他正在担心列表上的一个较高优先级的问题。他匆忙地解决较高优先级的问题,但是没想到编写任何回归测试用例,只是迅速编写该问题的补丁然后发送给维克托。维克托迅速找到了迈克尔补丁中不完整的回归测试用例。迈克尔之前并不知道应该编写回归测试用例以确保软件功能不出现退化。

发布新补丁的过程未被正确记录,而像迈克尔这样的新开发人员又频繁地创造破坏现有工作的回归。维克托向迈克尔讲解回归测试的概念后,迈克尔很快编写了一个带有回归测试用例的软件补丁。

维克托准备好所有的软件补丁后,从热修复(hotfix)版本开始,在本地计算机上重新运行所有测试。贝蒂收到一个包含最新版本的新ZIP文件,再次开始手动测试流程。QA 团队手动测试产品的各个部分,而测试产品的所有部分是一项非常耗时的工作。贝蒂找到了最新版本的一些问题并给维克托发送了一份较小的问题列表,以便维克托在本周稍晚的时候开始工作。

大卫被维克托突然打断并被告知放弃他的新功能开发工作,因为最新的改动存在缺陷。大卫花了 2 小时努力让自己转向最新版本的问题。当他确定已追踪到问题后,他花了整个下午的时间来进行修复。大卫向维克托报告最新的改动已准备好测试。维克托开始在自己的本地工作站上运行测试,立刻看到一些集成测试由于最新的改动而失败,他通知大卫这些问题必须解决。大卫现在很沮丧,一直工作到深夜进行再次修复。第二天早上,维克托运行了所有测试并全部通过,于是他向贝蒂发送了最新的热修复版本的ZIP 文件。贝蒂在第二天开始手动测试流程,遗憾的是,她再次发现了两三个小问题,并让维克托在下午知道了最新版本中仍然存在一些问题。

此时已经非常沮丧的维克托将所有的开发人员聚集在一起,并说在所有的问题都被解决之前谁都不许离开。在办公室待了一个漫长的夜晚后,所有的问题都得到了解决,于是维克托让所有人都回家。第二天早上,维克托将最新版本打包并将新的ZIP文件发送给贝蒂。贝蒂在上次的测试后有些担心,但她十分高兴所有的错误都已被解决,她发了QA批准印章,并告知维克托最新发行版本已经准备就绪。开发团队和QA团队用公司提供的午餐庆祝一周的工作结束,并回家度过周末。

在QA团队测试热修复版本时,一些QA团队成员的工作重复了。迪利恩因为自己的一些工作与贝蒂做过的重复而感到很沮丧。QA 团队没有自动化,因此无论是补丁还是常规发行版本,每个版本的所有工作都是手动完成的,并且QA团队必须重新测试UI的所有部分。QA团队的新成员内特询问迪利恩是否有比手动测试更好的工作方式,却被告知这样的方式在QA团队中已经是惯例了。

托尼是客户成功团队的一名成员,他花费了大量时间为客户X制作新视频,但随即收到通知,他的一些视频不允许被发布,且只能入库,所以他对新的发行版本感到很沮丧。QA团队在最后一刻决定终止功能Y,但没有与其他团队沟通这一信息。

首席销售工程师之一艾森特正在进行演示,并向一位潜在客户展示了导出PDF功能。在演示过程中,维克托单击了导出PDF功能的按钮,却弹出了明显的错误消息。维克托迅速转而演示产品的另一个功能,并表示这是暂时的故障,他会在之后的另一演示中对此进行展示。维克托发现某位开发人员对后端服务之一进行了简单的更改,并在生产环境中破坏了导出PDF功能。然后维克托发现潜在客户打算采用另一款软件产品了,显然他现在非常烦恼,他的年终奖就指望这位新客户了。

UI/UX团队的成员萨曼莎收到通知,她的一份视觉稿缺少校验流程。然后萨曼莎在功能Z的原型设计阶段留存的资料上确认了,该页面不需要任何校验流程,但大卫认为需要校验流程。萨曼莎被搞得心烦意乱,她决定休假,现在大卫关于功能Z的工作已经落后于原定计划。

图1-2展示了沟通关系。

 

QA团队的贝蒂和开发团队的维克托之间存在双向沟通。在寻找能够自动化的工作领域时,沟通至关重要。随着各方之间交互次数的增加,参与的各方对手动流程的了解也随之增加:当市场营销、销售、客户成功以及开发等团队多方开始更频繁协作时,很多隐藏的手动流程就会暴露出来。找寻手动流程的工作更适合让开发人员去做,因为对于非开发人员来说,一个流程是不是手动的,并且可以自动化,并不那么容易分清楚。

图1-2

图1-3是一个叫乔尼自动化机器人(automation bot),用于描述公司中的不同团队。乔尼的各部分肢体代表公司中的各个团队。

图1-3

自动化机器人乔尼说明了能够从自动化过程中获益的领域。我们可以将自动化看作通过机器完成人类日常工作的程序或系统。要做到自动化,需要了解正在使用哪些手动程序,与其他团队沟通,并查找哪些流程是手动运行的。本书后文将提到的CI和CD,能够大幅提升公司生产力和优化生产流程,因为这种方法不再依赖开发人员的常用条件和特定的环境配置。

 

自动化机器人乔尼的每个部分都有一个领域适合自动化。销售团队目前通过电子邮件给本团队发送Excel表格,但难以将销售信息与其他销售工程师做出的更改保持同步。自动化机器人乔尼建议销售工程师将销售信息上传至公司内部网(intranet),以便更好地保持销售信息的流通。乔尼建议开发团队编写一个Excel整合表,这样销售工程师便可轻松地将新的销售数据上传至公司内部网。例如,可以添加一个与公司API端点(endpoint)“挂钩”的菜单选项,该选项能自动将新的Excel更改上传至含有最新销售信息的公司内部网页面。

QA 团队手动测试产品,这是一项非常耗时且易出错的工作。乔尼建议QA团队使用Selenium WebDriver编写验收测试。Selenium是一个浏览器自动化工具,QA团队可以使用Python之类的语言编写验收测试。乔尼认为使用Selenium编写自动测试的优点是可以一次编写反复使用。这么做的附带好处是这些测试可以与CI/CD流水线挂钩,这一点在本书后文将会提及。

QA 团队的贝蒂发现客户成功团队正在制作一系列视频,这些视频教客户如何在新构建中使用新功能。客户成功团队通过FTP上传视频,这种上传方式耗费了团队大量时间。自动化机器人乔尼建议这个过程通过脚本实现自动化。脚本需要足够直观以便团队中的所有成员都能运行,而且脚本应该完成上传工作并在出现网络延迟时重试上传。贝蒂分享了一个QA团队写好的脚本,其运行时会作为后台进程自动运行上传过程。

客户成功团队的托尼现在结束了工作日连续数小时的工作,可以专注于岗位上其他更重要的部分,比如制作精彩的视频来获得客户。托尼和QA团队开始工作,打算发布视频并在部分产品上进行用户验收测试。由于手动测试已经被交给客户成功团队,QA团队现在可以更好地测试功能。团队现在专注于使用自动化、端对端的测试套件,这个套件带有能帮助他们更快编写测试的库,还能反过来通知开发人员已损坏的功能。

市场营销团队一直在PowerPoint演示文稿中嵌入笔记,有时,这些笔记会在演示过程中丢失或被覆盖。乔尼建议开发团队编写脚本把PowerPoint演示文稿转成Markdown格式,因为Markdown文件是纯文本文件,能够进行版本控制。这样做还有额外的好处:市场营销团队可以与销售团队共享信息以创建更具说服力的图表。

维克托意识到手动流程正在破坏公司的生产力,而且有明显缺点,他打算在版本发布流程中引进一套自动化系统,团队中的开发人员只需单击部署按钮便可运行系统。不同于维克托在本地工作站上运行所有测试的工作模式,每个软件版本都能被推送(push)到一套版本控制系统(如GitHub),而且所有的测试都能在CI环境(如Jenkins)中运行,开发人员会收到测试通过或失败的自动通知。以团队新进入的开发人员布鲁斯为例,他可以迅速阅读开发文档然后开始做下一个版本,几乎不需要另外的指导。自动化机器人乔尼表扬了这次实践。

贝蒂也有将手动测试流程自动化的机会。通过使用BrowserStack之类的工具,贝蒂可以编写一系列测试脚本来测试产品的各个部分。在一小时内,贝蒂能够在测试环境里运行一组验收测试并让维克托了解到这个版本中的最新问题。维克托随即将问题分配给开发团队并让其开始编写回归测试用例,以确保当前版本没有出现回归。维克托确信最新的更改会按设想运行,他给了贝蒂一个新的统一资源定位符(uniform resource locator,URL),以便她从中下载最新版本的软件。自动化机器人乔尼指出之前创建ZIP文件并以电子邮件发送的做法不是一个好习惯,因为每次都需要额外的步骤,而且如果发送了错误的ZIP文件则容易出错。乔尼建议QA团队使用专用URL,将所有的最新发行版本存入其中,对每个版本实行版本控制并声明特定信息(如热修复)。例如,最新的热修复程序的版本标签可以是v5.1.0-hotfix1,对于每个修补程序,QA团队都要使用具有最新版本和说明符(如hotfix)的压缩文件。如果此构建是常规构建,则可将其命名为v5.1.0。

维克托发现QA团队有一个BrowserStack账号。BrowserStack提供对整套浏览器和移动客户端的访问接口,有助于将UI的负载测试自动化。开发团队使用自定义服务器来完成负载测试之类的特殊场景的工作。自动化机器人乔尼建议使用BrowserStack之类的服务或者提供必要资源进行负载测试的自定义服务。

维克托发现QA团队在测试开发团队编写的电子邮件服务时遇到了问题。自动化机器人乔尼建议开发团队确认QA团队手上所有能够让电子邮件服务配合工作的脚本。维克托告诉贝蒂新的电子邮件服务使用了SendGrid代理服务,而且开发团队已经编写了一系列可供QA团队使用的脚本。这些脚本有助于编写测试,并可以帮助QA团队测试故障时的具体情况。

UI/UX团队正在将视觉稿上传到Sketch——一种原型设计工具,并将可能用到的与验证状态和流程相关的注释插入页面中。这些注释非常详细,对处于公司短期冲刺(sprint)中、正在进行功能开发的开发团队十分有帮助。自动化机器人乔尼建议开发团队编写一个插件,帮助UI/UX团队轻松地共享这些信息。维克托决定创建一个Sketch插件,该插件能创建包含内嵌注释的PDF文件,UI/UX团队可以在原型设计完成后通过电子邮件将其发送给开发团队。对于UI/UX团队而言,此插件易于安装,他们只需双击文件便可自动安装插件。访问PDF文件和嵌入的注释将帮助开发人员了解新功能的用例和UI流程。

首席销售工程师文森特已与开发团队沟通,他需要知道产品中的流程更改,尤其是在与潜在客户讨论公司路线图上的新功能时。自动化机器人乔尼建议开发团队使用Git提交日志,日志应包含关于最新功能更改的详细信息。维克托编写了一个脚本来抓取Git提交日志,并编写了一个包含所有最新功能的Markdown文件。客户成功团队还能与开发团队合作,使用Markdown文件在公司博客上创建精美的博客条目,详细介绍所有的最新功能。

 

这里有一个共同的主题:团队之间的沟通是找到手动流程和建立有助于自动化流程的伙伴关系的关键。只有了解手动流程,自动化才能实现,而且有时实现自动化的唯一方法是由其他团队传达特定的痛点。

让我们重申一些通过开放式协作实现自动化的流程。维克托通过提供开发团队创建的脚本,帮助QA团队将电子邮件服务测试的问题自动化。QA团队通过共享上传视频并具有重试逻辑的脚本,帮助客户成功团队实现视频上传任务的自动化。销售团队表达了对产品新功能更高可见度的需求,于是开发团队编写了一个脚本,从Git提交日志中获取信息以生成Markdown文件,客户成功团队使用该脚本在公司博客中编写精美的博客条目。现在,UI/UX团队已经将一个插件集成到他们的Sketch应用程序中,只需单击一个按钮即可生成含有原型阶段注释的PDF文件,从而帮助开发团队开发新功能。开发团队发现QA团队正在使用一个名为BrowserStack的工具,并开始使用它对产品进行负载测试。市场营销团队现在拥有营销演示文稿的副本,并且正在与销售团队共享此信息,以创建用于公司演示的新图表。

UI/UX团队已决定创建样式指南,开发人员可以在其中找到软件产品的常见UI样式。UI/UX团队发现不同的页面中使用了许多不同的样式,这会使许多客户感到困惑。例如,零件供应页面上有一个大的蓝色的保存按钮和一个红色的取消按钮,但是在供应商详情页面上是一个大的红色的保存按钮和一个蓝色的取消按钮。客户会因为UI没有使用统一配色而单击错误的按钮。有时,页面使用确认模式来添加和删除项目,但其他时候又没有使用确认模式。UI/UX团队已开始研究样式指南,并在将要存放实时样式指南的公司内部网中创建一个特殊的URL,用来为页面显式创建和列出所有可用的十六进制颜色、设计产品中所有的按钮,以及确定表单在页面上的外观和行为。

此外,将有一个特殊的窗口部件(widget),为产品中所有的专用部件嵌入HTML标记和样式,样式指南示例如图1-4所示。

图1-4

 

图1-4展示的样式指南示例具有十六进制颜色值,并嵌入了一些HTML元素和一个拨动开关(一个只有关闭状态和打开状态的专用部件)。样式指南的目的是使开发人员简单地单击鼠标右键便可复制HTML标记和CSS样式,并建立统一的UI展现形式。这是一种自动化形式,因为开发人员可以简单地重用现有标记和样式,而不必手动创建最应该统一的HTML和自定义样式。任何让产品客户不得不猜测该怎么做的情况,都会导致灾难。

由于维克托在构建中实现了CI/CD流水线,许多耗时的活动现在都转移到了自动化流水线中。每当软件被推送到Git之类的版本控制系统(version control system,VCS)的upstream时,就会在Jenkins中触发自动构建,然后该构建会运行所有的单元测试和集成测试。开发人员可以迅速得知他们编写的代码是否引入了缺陷。请记住,维克托必须合并所有软件补丁并在他的本地工作站上手动运行所有测试。这是冗长、耗时且不必要的。

当所有的软件被推送到upsteam后,维克托为版本分支设置了代码截止日期,并开始对软件发行版本的二进制文件进行版本控制,以便QA团队更清楚地描述每个构建。维克托可以开始将发行周期委派给团队中的其他开发人员,因此更加高效。所有开发人员都可以在版本中记录发行周期中遇到的任何问题。维克托现在有更多时间开始计划下一个软件周期,以及指导团队中的初级开发人员。大卫现在很高兴,因为他可以将他的最新更改推送到源码控制(source control),并使所有测试都在CI环境中运行,他对自己的更改能够按预期工作更有信心了。

贝蒂已经建立了一组完整的验收测试,以检查软件产品的每个部分。产品中的任何回归都会立即在CI环境中显露,而且所有的测试都能每天运行。由于QA团队正在运行的测试是端对端测试,相比开发团队,QA团队的测试更耗时且占用资源,但QA团队的优势在于所有测试每天运行,而且每晚都会拿到关于任何测试失败的详细报告。贝蒂编写了一组页面对象,帮助QA团队中的其他成员重用其他测试脚本并缩短测试周期。现在,贝蒂在QA周期中有了时间,可以指导QA团队的新成员进行测试实践并学习如何为开发团队正确地标记问题,以便开发团队知道问题在最新版本中的位置。

大卫现在可以开始帮助维克托指导团队中的初级开发人员,此外,开发团队开始进行午餐交流会活动,其他开发人员也可以在团队中分享知识。开发团队很快意识到这种午餐交流会也适用于QA团队。在一次午餐交流会中,QA团队建议开发团队进行一次更改,以协调QA团队和开发团队之间的版本发布工作。通过这种合作关系,版本发布周期从一个星期的时间缩短为3小时。开发团队轮流进行版本发布工作,以便团队中的每个开发人员都能学习如何进行版本发布(如图1-5所示)。轮值负责的开发人员确保QA团队拥有可用于测试的版本,且该版本可使用持续集成系统(如Jenkins、Travis或CircleCI)自动触发。在这些CI环境中,可以设置在指定日期和时间运行的版本触发器。QA团队会将版本中的任何回归情况报告给开发团队,并且每当开发团队准备推出一个热修复程序时,就使用vMAJOR.MINOR.PATH-[hotfix]-[0-9]*模式清晰地描述版本。为了清楚起见,以下是一个示例:v6.0.0-hotfix-1,表示主要版本为6、次要版本为0、补丁版本为0、热修复编号为1。这种命名方案有助于QA团队区分常规版本和热修复版本。

图1-5

客户成功团队已与开发团队交流了客户使用情况,一些客户在使用Billy Bob’s Machine Parts公司的应用编程接口(application programming interface,API)服务时遇到了问题。客户成功团队询问开发团队,是否有方法可以帮助新的第三方API使用者(consumer)入门。需要说明的是,API使用者是消费/使用现有API的人,而API提供者是维护实际API服务的人。因此,从这方面来说,Billy Bob’s Machine Parts公司是API提供者,为第三方开发者提供了一个运行中的API。开发团队告诉客户成功团队,他们想创建一个开发者门户(developer portal),帮助API使用者轻松使用API。然而开发团队很难说服高层管理者认可开发者门户的价值,因为没有人要求提供这个功能。客户成功团队迅速说服高层管理者,对于Billy Bob’s Machine Parts公司的API使用者来说,开发者门户将是一笔巨大的财富,而且API使用者可以开始使用API服务中的数据来构建美观的仪表盘。

在一次开发人员会议中,大家发现市场营销团队正在使用Google Docs共享文档,但由于必须准确查询信息,使用者很难找到上传的内容。维克托意识到开发团队可以建立公司内部网,帮助销售团队和市场营销团队以更一致的方式共享数据。几个月后,公司内部网投入使用,销售团队和市场营销团队激动地提及公司内部网已帮助他们实现了文档共享流程的自动化。过去,召开销售团队和市场营销团队间的许多会议都需要浪费大量时间来查找所需文档,而公司内部网采用了一种过滤机制,可以通过标签系统快速找到文档。此外,公司内部网还启用了一个新功能,使销售团队和市场营销团队能够编辑共享文档。

销售团队现在有强大的工具来建设公司博客、展示新的产品功能。维克托可以查看公司博客,以查找产品中的最新功能。这一切都归功于维克托编写的脚本。该脚本能获取Git提交日志中的提交信息(commit message),并将其发布为Markdown文件。脚本被用于每一次版本发布,生成所有已处理项目的清单,开发团队将新生成的Markdown文件发送给客户成功团队,以便他们基于此文件编写精美的博客条目,介绍最新版本的所有细节。

QA团队开始处理工单(ticket),其中的零件限制导致了特定的UI错误。如果客户在产品详情页面上列出超过10 000个零件,产品详情页面将崩溃,且不会提供关于当前状况的指示。开发团队发现QA团队正在新产品页面中手动创建新产品,他们帮助QA团队找到管理员端点,通过程序创建语音邮件。开发团队编写了一个脚本,用程序生成新零件,从而使QA团队免于执行耗时的手动创建零件的任务。

实现自动化,必须打破团队间的沟通障碍。有时,不同的团队认为他们考虑的是同一件事情,但实际上他们在谈论不同的事情。

为了消除误解,开放沟通渠道非常重要,如图1-6所示。

图1-6

有趣的是,在发布周期内还有更多空间可以实现自动化。维克托向贝蒂询问源码控制中一些验收测试的情况,他意识到他可以将验收测试集成到CI环境中,并创建一个二级构建(secondary build),供所有验收测试每天晚上运行,则QA团队将在每天早上获得关于最新测试失败的详细报告。然后,QA人员可以在每天上午重新检查失败的验收测试,并通知开发团队是功能X损坏(如零件供应页面),而负责这个功能的开发人员则需要重新核对新的业务逻辑。

大卫开始与UI/UX团队交谈,并发现新公开的API端点与新页面的构造之间存在瓶颈。前端开发人员正在模拟这些页面中的数据,并不断因意外的JSON净荷(payload)而感到惊讶。前端开发人员有时会等待数周才能发布API端点,但他们并不会徒然等待,而是开始模拟数据。这产生了意想不到的结果:他们开始假定数据模型是什么样的,从而使更改页面变得更加困难。大卫告诉维克托有一些工具可以为API端点迅速搭建数据模型,并为前端开发人员提供API端点当前的数据模型。大卫开始使用Swagger(一种API设计框架)作为在API服务中构建新API的工具。Swagger有助于减少开发团队和UI/UX团队之间不必要的“摩擦”,这些摩擦通常是因为UI/UX团队需要等待数据模型而产生的。高级UI/UX开发人员贾森现在可以快速开始构建新页面,因为他明确知道新的API端点应该出现哪种类型的有效负载。

QA团队成员阿曼达已经开始与客户成功团队合作进行负载测试和用户验收测试。他们在用户验收测试周期中添加了验收测试,以揭示核心产品中UI/UX团队可以改进的区域。客户成功团队现在承担了测试新页面和发现潜在UI问题的额外责任。验收测试非常适用于测试令人满意的路径方案,因为一切都按预期完成时,用户验收测试仍可以揭示UI中不直观的工作流程。例如,拉里在零件供应页面中测试新的过滤功能时,发现要使过滤器开始工作,需要单击一个复选框。拉里询问QA人员,为什么默认情况下无法进行过滤,以及为什么需要一个复选框。开发人员随即开始添加默认过滤,零件供应页面如图1-7所示。

图1-7

 

图1-7中不显示复选框,仅显示使用了输入文本框的页面,且每当客户按回车键、逗号或制表符时,都会应用新的过滤器,页面会被自动过滤。如果没有要显示的结果,页面将显示文本“未找到结果”。

客户成功团队的成员贾斯汀询问QA团队的弗朗西斯,他是否可以借用QA团队测试过的新功能的视频。贾斯汀发现QA团队拥有一组非常有价值的视频,客户成功团队可以利用这些视频来教客户使用最新功能。当QA团队发布新视频时,弗朗西斯创建了一个内部门户网站供客户成功团队使用。客户成功团队一直在为新客户创作入门视频,并设计了一个知识门户网站,说明如何进行设置,例如设置新的零件供应页面等。

销售团队一直将与客户和潜在客户进行讨论的记录发送到个人的电子邮件账号。文森特发现销售经理哈利最近丢失了一些有价值的记录,因为他不小心删除了与潜在客户共进午餐时所记的笔记。维克托告诉哈利,有一个新的公司内部网,其中的一个项目页面含有哈利可以创建的卡片,因此销售团队可以为每个潜在客户创建一个销售平台。哈利为一位潜在客户创建了新的销售平台,并与首席销售执行官吉姆分享。吉姆非常激动,因为他意识到公司内部网还可以用于创建图表。吉姆使用图1-8向首席销售执行官展示最新的销售客户。

图1-8

 

销售团队可以为每个潜在客户创建一个销售平台。公司内部网正在帮助整合公司内部的更多团队,因为团队成员正在打破沟通障碍。

开发团队发现,客户成功团队一直在将UI用于耗时且容易出错的流程。开发主管埃里克提出了一个方案:创建一个命令行界面(command line interface,CLI)供客户成功团队使用,以自动化当前工作流程中含有手动流程的许多部分。开发人员解释了CLI如何为客户成功团队节省时间,还能帮助API使用者更好地使用API服务。CLI可用于为UI快速提供重要的页面数据。由于每个新版本都可能提供新的API端点,因此开发团队创建了一个方案,以便向CLI中添加用于新的API端点的附加命令。

CLI应用将与开发者门户的提案同时工作,并促进API使用者采用。与此同时,开发团队决定启动一项软件开发套件(software development kit,SDK)计划,API使用者可以将其与API一起使用。SDK可以极大地改善和增加第三方API提供者的采用,从而可以提高API的采用率。SDK十分有用,因为开发人员和QA人员使用不同的编程语言。机械零件API的开发人员使用Go语言工作,而QA人员在大多数工作中使用Python。SDK将支持多种编程语言,并帮助API使用者快速启动并运行API,因为他们可以选择自己喜欢的语言来使用API服务。

为了使手动流程自动化,公司内不同的团队之间必须进行沟通。在一个公司的所有团队中,必然会存在手动流程。开发团队、QA团队、客户成功团队和UI/UX团队的领导开始每个月开一次会,以讨论更新的实践,并开始在公司中寻找需要自动化的其他手动流程。

手动流程并不是天生就是糟糕的,用户验收测试(user acceptance testing,UAT)在公司中仍可以有效完成,并能暴露出自动测试无法发现的问题。UAT对测试出自动测试有时无法发现的极端问题十分有用,如之前的示例展示的那样,客户成功团队测试了一个新功能,发现零件供应页面仅在选中复选框的情况下才启用过滤。

市场营销、销售和客户成功团队通常使用电子表格应用程序(如Excel)来计算数字,并使用演示文稿应用程序(如PowerPoint)来创建图表。通常,在Excel表格中计算出的数字会保存在修订版中,但团队成员必须通过电子邮件将副本发送给其他团队成员。开发人员可以要求市场营销、销售和客户成功团队以逗号分隔值(comma-separated value,CSV)格式导出Excel表格中的值,该格式是文本文件,更易于使用。这种做法具有附加价值,比如公司内部网可以使用数据可视化工具来创建精美的HTML图表和演示文稿,D3之类的库可用于创建许多功能强大的可视化类型。

为了使团队开始合作并公开讨论问题,团队中必须存在一种开放的精神。团队孤立起来非常容易,但这意味着他们与其他团队正在做的事情脱节。开发团队可以直接选择与QA团队保持联系。沟通是暴露团队之间手动流程的关键,如果没有沟通,团队将独立处理他们自认为重要的项目。

不同团队共同参与一些社交活动有助于打破障碍并建立友好的环境。通常,开发人员参加会议只是为了与其他开发人员进行互动,而且常常会有一条走廊,开发人员站在会场外面的这条走廊里,仅与其他开发人员交谈,而不参加会议中活跃的分会场。

公司可以赞助社交活动并协助任命不同团队的代表,以帮助团队“破冰”。例如,在公司打保龄球的活动中,人们可能被故意分入不同的团队。一支小型保龄球团队可以由开发团队成员、客户成功团队成员、QA团队成员、市场营销团队成员和销售团队成员组成。这可以激发合作关系,使团队成员彼此了解并公开交流他们在公司活动之外遇到的问题。

Billy Bob’s Machine Parts公司安排了一场棒球比赛,开发主管埃里克、QA负责人贝蒂与几个市场营销团队、销售团队以及客户成功团队的成员一起负责。他们为棒球比赛组建了两支球队,并在赛后安排了公司烧烤,以便大家可以一起吃饭和交谈。

另一种鼓励积极合作的方法是更改​​公司的楼层设计,使其更加开放。许多软件公司之所以采用开放式的楼层设计,是因为这样消除了小隔间在人与人间造成的自然隔阂。如果有一个开放式的空间,员工就更有可能与不同的团队接触,因为这样可以轻松地走向不同的人,而不会觉得自己正在“入侵”他人的空间。

沟通是找到手动流程的关键,而且找到手动流程并将其自动化非常重要。正如本章的各种业务场景中所说明的那样,手动流程往往容易出错且非常耗时,这些流程可以通过实现CI构建和编写使手动流程自动化的脚本等方法来自动化。开发人员和QA人员可以开发自动化脚本,使销售、市场营销和客户成功等许多团队从中受益。在本章中,我们已经了解到自动化相比手动流程的优势,以及开放式通信的价值。

在第2章中,我们将学习CI的基础知识。

1.什么是手动流程?

2.什么是自动化?

3.为什么团队之间的开放沟通很重要?

4.CI/CD表示什么?

5.为什么自动化脚本是有用的?

6.公司内部网的价值是什么?

7.为什么团队之间应该共享数据?

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e58472”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。

本章将介绍持续集成(continuous integration,CI)的概念,并帮助读者建立基础的CI/CD概念,以便在后面的章节中进行深入探讨。了解CI构建的用途非常重要,因为这些概念超越了任何给定的CI/CD工具。持续集成很重要,因为它有助于保持代码库的良好状况,并帮助开发人员使软件系统独立于任何特定的开发人员的计算机而运行。CI构建增强了软件组件和本地环境配置的独立性,它应该与开发人员的任何一种配置分离,应该能够重复且在状态上是孤立的。每个CI构建的运行在本质上都应该是独立的,因为这确保了软件系统的正常运作。

本章涵盖以下内容:

什么是持续集成;

持续集成的价值;

利用持续集成降低风险;

源码签入时的软件构建;

小型构建和大型构建故障;

CI构建实践。

本章仅假定读者对版本控制系统有粗略的了解,但读者至少应该了解什么是配置文件,并对编程有基本的了解。本章将简要介绍一个makefile示例,并提供一些代码段。

我们将在本章中学习一些代码示例,包括一个API工作坊(作者将在其中解释一个makefile和一个使用了React/Node.js/Express.js/RethinkDB的示范程序),本章还将展示一个gulp.js脚本文件。

持续集成本质上是一项软件工程任务,源码在主线上被合并和测试。一项持续集成任务可以执行包括测试软件组件和部署软件组件在内的许多任务。持续集成的行动在本质上是规定性的,可以由任何开发人员、系统管理员或操作人员执行。持续集成之所以持续,是因为开发人员可以在开发软件时不断集成软件组件。

软件构建不只是编译步骤,它可以由编译步骤、测试阶段、代码检查阶段和部署阶段组成。可以将软件构建当作一种验证步骤,以检查软件是否作为内聚单元工作。静态编译的语言(例如Go语言和C++语言)通常具有生成二进制文件的构建工具。以Go语言为例,使用构建命令go build会生成静态编译的二进制文件并在代码库上运行语法检查(linting)。其他语言(例如JavaScript)可以使用gulp.js/grunt.js之类的工具来运行构建步骤,例如缩减(minification,即将多个JavaScript源文件转换成一个文件)和丑化(uglification,即移除源文件中的注释和空白字符、做语法检查并运行测试运行程序)。

开发人员可以将代码提交到版本控制项目(version control project,VCP)系统,例如GitHub和GitLab。CI服务器可以轮询(poll)存储库以查找更改,也可以将CI服务器配置为通过Webhook触发软件构建,后续我们会将其与Jenkins、Travis CI和CircleCI一起探讨。然后,CI服务器会从VCP系统中获取最新的软件版本,随即运行集成该软件系统的构建脚本。CI服务器应生成反馈,在生成失败时通过电子邮件将构建结果发送给指定的项目成员。CI服务器将持续轮询更改或来自所配置的Webhook响应。

持续集成的价值体现在很多方面。最重要的是,CI构建可以降低风险,而且可使软件的状况变得可以衡量。此外,持续集成有助于减少开发人员的主观假设。持续集成环境不应依赖于环境变量,也不应依赖于任何个人计算机上设置的某些配置文件。

CI构建应干净整洁且独立于每个开发人员的本地计算机,并且应与所有本地环境分离。如果开发人员说某个构建可以在他/她的计算机上运行,但是其他开发人员无法运行同样的代码,那么该构建可能无法正常运行。CI构建有助于解决此类问题,因为CI构建与任何给定的开发人员的设置和环境变量都是分离的,并且独立于它们运行。

CI构建应减少重复的手动流程,而且CI构建过程应在每个构建上以相同的方式运行。CI构建过程可能包括编译步骤、测试阶段和报告生成阶段。CI构建过程应在开发人员每次将提交推送到版本控制系统(如Git、Subversion和Mercurial)时运行。CI构建应能使开发人员腾出更多精力从事更高价值的工作,并能减少重复的手动流程造成的错误。

良好的CI构建应有助于随时随地生成可部署的软件。CI构建应实现项目可见性,并使开发团队建立对软件的信心。开发人员可以相信,与在本地运行构建相比,CI构建将更能捕获代码更改问题。

持续集成可以帮助降低软件构建中普遍存在的风险,比如“但它在我的计算机上就能运行”的风险。持续集成还可以帮助统一发生故障的集成点,例如数据库逻辑以及许多其他类型的问题。

1.“但它在我的计算机上能运行”

开发人员之间的共同点是,软件构建可以在一个开发人员的计算机上运行,而不能在另一个开发人员的计算机上运行。每个开发人员的计算机都应尽可能紧密地镜像软件集成。进行软件构建所需的一切都必须提交至版本控制系统。开发人员不应该有仅存储于本地计算机上的自定义构建脚本。

2.数据库同步

完成软件构建所需的任何数据库工件(artifact)都应存储在版本控制系统中。如果有相关的数据库,则任何数据库创建脚本、数据处理脚本、SQL存储程序和数据库触发器都应存储在版本控制系统中。

例如,如果开发人员拥有NoSQL数据库系统(如MongoDB),并且正在使用RESTful API,那么需要确保在文档中记录API端点。请记住,开发人员可能需要特定于数据库的代码才能实际运行软件构建。

3.缺少部署自动化阶段

软件部署应通过部署工具实现自动化。根据不同的软件体系结构,开发人员使用的部署工具可能会有所不同。

下面是一些部署工具:

Octopus Deploy;

AWS Elastic Beanstalk;

Heroku;

Google App Engine;

Dokku。

部署工具很有价值,因为它们往往跨平台,并且能在许多不同的软件体系结构中使用。例如,如果开发人员编写了一个Bash脚本,则存在一个基本假设,即其他开发人员正在使用类Unix的操作系统,而且在Windows环境中工作的开发人员可能无法运行脚本,这具体取决于他们正在使用的Windows版本。Windows 10现在提供了一个Bash子系统,Windows开发人员可以在运行Windows的同时在这个子系统中执行Unix命令和运行Unix脚本。

4.软件缺陷发现太晚

CI构建可以帮助我们防止软件缺陷发现太晚。CI构建应具有足够好的测试套件,以覆盖大部分代码库。衡量代码库状况的一种可能的指标是在代码库中达到70%或更高的代码覆盖率。后面将讨论代码覆盖率,但是任何软件测试都应进入源码进行检查,且测试应在CI构建上运行。所有软件测试都应在持续集成系统上持续运行。

5.测试覆盖率未知

通常,较高的代码覆盖率表示代码库已经过充分测试,但不一定保证代码库没有软件错误——只是测试套件始终具有良好的测试覆盖率。可尝试使用代码覆盖率工具来查看实际有多少测试覆盖了源码。

以下是一些流行的代码覆盖率工具。

Istanbul:一个JavaScript代码覆盖率工具,使用模块加载器挂钩计算语句、行、函数和分支覆盖率,以在运行测试时透明地增加覆盖率。其支持所有JavaScript覆盖的用例,包括单元测试、服务器端功能测试和浏览器测试。专为大规模测试打造。

Goveralls:代码覆盖率持续跟踪系统Coveralls的Go语言集成。

dotCover:JetBrains dotCover是与Visual Studio集成的.NET单元测试运行程序和代码覆盖率工具。

开发人员需要确保自己了解单元测试覆盖代码的程度。dotCover能在针对.NET Framework、Silverlight和.NET Core的应用程序中计算并报告语句级别的代码覆盖率。

6.缺乏项目可见性

持续集成系统应配置为以多种方式发送警报:

电子邮件;

短信;

通过智能手机推送通知警报。

一些软件开发办公室还会使用其他有创造性的方式来发送关于软件构建的问题通知,例如以某种环境光的变化,甚至是对讲系统。关键在于要让开发人员得知CI构建已损坏,这样他们就可以快速修复构建。 CI构建不应该一直中断,因为这会影响其他开发人员的工作。

在版本控制系统中,每个源码的签入都应触发软件构建。下面介绍部署流程中的一个重要步骤,后面将会进一步说明。

1.软件构建

一个软件构建可以仅由编译软件组件构成。构建可以包括编译和运行自动化测试,但通常添加到构建中的进程越多,反馈循环在构建中的速度就越慢。

2.脚本工具

建议使用专门用于在个人脚本上构建软件的脚本工具。自定义的shell脚本或批处理脚本往往不是跨平台的,而且可能隐藏了环境配置。对于开发一致、可重复的构建方案,使用脚本工具是非常有效的方法。

下面是一些脚本工具:

Make;

Maven;

Leiningen;

Stack。

3.进行单命令构建

力求进行单命令构建以简化构建软件的过程,因为让运行构建过程越容易,就越会加快采用速度并促进开发人员参与。如果进行软件构建是一个复杂的过程,那么最终将只有很少的开发人员实际参与构建,这不是理想的情况。

4.简述构建软件

使用脚本工具,如Ant、Make、Maven或Rake。

从CI构建的简单流程开始。

添加每个流程以将软件集成到构建脚本中。

从命令行或IDE运行脚本。

下面是一个运行Go语言API服务的makefile示例,该示例来自作者的开源代码库:

BIN_DIR := "bin/apid"
 APID_MAIN := "cmd/apid/main.go"
all: ensure lint test-cover
ensure:
 go get -u github.com/mattn/goveralls
 go get -u github.com/philwinder/gocoverage
 go get -u github.com/alecthomas/gometalinter
 go get -u github.com/golang/dep/cmd/dep
 go get -u golang.org/x/tools/cmd/cover
 dep ensure
lint:
 gometalinter --install
 gometalinter ./cmd/... ./internal/...
compile: cmd/apid/main.go
 CGO_ENABLED=0 go build -i -o ${BIN_DIR} ${APID_MAIN}
test:
 go test ./... -v
test-cover:
 go test ./... -cover
## Travis automation scripts
travis-install:
 go get -u github.com/mattn/goveralls
 go get -u github.com/philwinder/gocoverage
 go get -u github.com/alecthomas/gometalinter
 go get -u github.com/golang/dep/cmd/dep
 go get -u golang.org/x/tools/cmd/cover
 dep ensure
travis-script:
 set -e
 CGO_ENABLED=0 go build -i -o ${BIN_DIR} ${APID_MAIN}
 gometalinter --install
 gometalinter ./cmd/... ./internal/...
 go test ./... -cover
 gocoverage
 goveralls -coverprofile=profile.cov -repotoken=${COVERALLS_TOKEN}

下面是一个使用gulp.js的构建脚本示例,该脚本从Sass源文件生成CSS构建并运行语法检查器(linter)。第一个代码块用于初始化变量和准备使用配置对象:

'use strict';

const gulp = require('gulp');
const webpack = require('webpack');
const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const uglify = require('gulp-uglify');
const concat = require('gulp-concat');
const runSequence = require('run-sequence');
const gutil = require('gulp-util');
const merge = require('merge-stream');
const nodemon = require('gulp-nodemon');
const livereload = require('gulp-livereload');
const eslint = require('gulp-eslint');

//加载环境常变量
require('dotenv').config();
const webpackConfig = process.env.NODE_ENV === 'development'
  ? require('./webpack.config.js')
  : require('./webpack.config.prod.js');

const jsPaths = [
  'src/js/components/*.js'
];
const sassPaths = [
  'static/scss/*.scss',
  './node_modules/bootstrap/dist/css/bootstrap.min.css'
];

const filesToCopy = [
  {
    src: './node_modules/react/dist/react.min.js',
    dest: './static/build'
  },
  {
    src: './node_modules/react-dom/dist/react-dom.min.js',
    dest: './static/build'
  },
  {
    src: './node_modules/react-bootstrap/dist/react-bootstrap.min.js',
    dest: './static/build'
  },
  {
    src: './images/favicon.ico',
    dest: './static/build'
  },
  {
    src: './icomoon/symbol-defs.svg',
    dest: './static/build'
  }
];

下面的第二个代码块是我们设置以下gulp任务的地方:复制React.js文件,对JavaScript文件进行丑化,创建构建JavaScript文件,以及从Sass文件中创建CSS文件。

gulp.task('copy:react:files', () => {
  const streams = [];
  filesToCopy.forEach((file) => {
    streams.push(gulp.src(file.src).pipe(gulp.dest(file.dest)));
  });
  return merge.apply(this, streams);
});

gulp.task('uglify:js', () => gulp.src(jsPaths)
    .pipe(uglify())
    .pipe(gulp.dest('static/build')));

gulp.task('build:js', (callback) => {
  webpack(Object.create(webpackConfig), (err, stats) => {
    if (err) {
      throw new gutil.PluginError('build:js', err);
    }
    gutil.log('[build:js]', stats.toString({ colors: true, chunks: false
}));
    callback();
  });
});

gulp.task('build:sass', () => gulp.src(sassPaths[0])
    .pipe(sourcemaps.init())
    .pipe(sass({
      outputStyle: 'compressed',
      includePaths: ['node_modules']
    }))
    .pipe(autoprefixer({ cascade: false }))
    .pipe(concat('advanced-tech.css'))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./static/build'))
    .pipe(livereload()));

gulp.task('build:vendor:sass', () => gulp.src([...sassPaths.slice(1)])
    .pipe(sourcemaps.init())
    .pipe(sass({
      outputStyle: 'compressed',
      includePaths: ['node_modules']
    }))
    .pipe(autoprefixer({ cascade: false }))
    .pipe(concat('vendor.css'))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./static/build')));

下面的最后一个代码块执行了一些监视器任务,这些任务将监视JavaScript文件和Sass文件中的所有更改,进行语法检查,并创建一个nodemon进程,该进程用于在任何文件有更改时重新启动Node服务器:

gulp.task('watch:js', () => {
  const config = Object.create(webpackConfig);
  config.watch = true;
  webpack(config, (err, stats) => {
    if (err) {
      throw new gutil.PluginError('watch:js', err);
    }
    gutil.log('[watch:js]', stats.toString({ colors: true, chunks: false
}));
  });
  gulp.watch('static/js/components/*.js', ['uglify:js', 'build:js']);
});

gulp.task('watch:sass', () => {
  gulp.watch('static/scss/*.scss', ['build:sass']);
});

gulp.task('watch-lint', () => {
  //仅检测在此表启动后更改的文件
  const lintAndPrint = eslint();
  // 格式化每个文件的结果,因为数据流不会结束
  lintAndPrint.pipe(eslint.formatEach());

  return gulp.watch(['*.js', 'routes/*.js', 'models/*.js', 'db/*.js',
'config/*.js', 'bin/www', 'static/js/components/*.jsx',
'static/js/actions/index.js', 'static/js/constants/constants.js',
'static/js/data/data.js', 'static/js/reducers/*.js',
'static/js/store/*.js', 'static/js/utils/ajax.js', '__tests__/*.js'], event
=> {
    if (event.type !== 'deleted') {
      gulp.src(event.path).pipe(lintAndPrint, {end: false});
    }
  });
});

gulp.task('start', () => {
  nodemon({
    script: './bin/www',
    exec: 'node --harmony',
    ignore: ['static/*'],
    env: {
      PORT: '3000'
    }
  });
});

gulp.task('dev:debug', () => {
  nodemon({
    script: './bin/www',
    exec: 'node --inspect --harmony',
    ignore: ['static/*'],
    env: {
      PORT: '3000'
    }
  });
});

gulp.task('build', (cb) => {
  runSequence('copy:react:files', 'uglify:js', 'build:js', 'build:sass',
'build:vendor:sass', cb);
});

gulp.task('dev', (cb) => {
  livereload.listen();
  runSequence('copy:react:files', 'uglify:js', 'build:sass',
'build:vendor:sass', ['watch:js', 'watch:sass', 'watch-lint'], 'start',
cb);
});

gulp.task('debug', (cb) => {
  livereload.listen();
  runSequence('copy:react:files', 'uglify:js', 'build:sass',
'build:vendor:sass', ['watch:js', 'watch:sass', 'watch-lint'], 'dev:debug',
cb);
});

5.将构建脚本与IDE分开

尽量避免将构建脚本耦合到任何特定的集成开发环境(integrated development environment,IDE)。构建脚本不应该依赖任何IDE。

这非常重要,有以下两个原因:

每个开发人员可能使用不同的IDE/编辑器,而且可能有不同的配置;

CI服务器必须运行自动构建,无须任何人工干预。

6.软件资产应集中化

以下软件资产应在集中的版本控制存储库上可用:

组件,例如源文件或库文件;

第三方组件,如DLL文件和JAR文件;

配置文件;

初始化应用程序所需的数据文件;

构建脚本和构建环境设置;

某些组件所需的安装脚本。

 

开发人员必须决定应将哪些内容纳入版本控制。

7.创建一致的目录结构

软件资产必须使用一致的目录结构,这有助于从CI服务器运行脚本检索。

下面是为React/Redux框架应用设置的目录结构示例。

ca(证书授权)。

config(配置文件)。

db(与数据库有关的东西)。

docs(文档)。

images(镜像)。

models(数据文件)。

test(所有的测试文件)。

unit。

integration。

e2e。

helpers。

static。

build。

js。

actions。

components。

constants。

data。

reducers。

store。

utils。

scss。

utils(实用程序文件)。

另一个目录结构是面向包的,由Go语言社区的比尔·肯尼迪(Bill Kennedy)推荐,示例如下。

kit。

为现有的不同应用程序项目提供基础支持的软件包。

日志、配置或Web功能。

cmd/。

为启动、关闭和配置而构建的特定程序提供支持的软件包。

internal/。

为项目拥有的不同程序提供支持的软件包。

CRUD、服务或业务逻辑。

internal/platform/。

为项目提供内部基础支持的软件包。

数据库、身份认证(authentication)或数据封送处理(marshaling)。

关键在于,应该为代码库制订一套所有开发人员都遵循的标准命名约定。这有助于开发团队工作,因为他们将熟悉代码中列出的特定内容。并非所有人都会同意特定的目录结构,但是拥有标准是最重要的部分。例如,开发新服务的任何人都应该能够基于文件夹、源文件所在位置以及测试文件所在位置的命名约定来建立目录结构。

图2-1是作者在GitHub中创建的API Workshop所使用的目录结构。

图2-1

8.软件构建应快速失败

这可以通过执行以下操作来实现。

(1)集成软件组件。

(2)运行真实的单元测试——不依赖数据库而是运行独立的单元测试。

(3)确保单元测试能够快速运行。如果单元测试需要几分钟的时间,则可能表明存在问题。

(4)运行其他自动化流程(如重建数据库、检查和部署)。

 

可能有其他的必要步骤,这取决于每个公司的构建需要。

9.适用于任何环境的构建

应该为不同的环境设置配置文件和环境变量,例如dev/prod/test。日志记录的详细程度应该能够根据环境进行设置。开发人员可能需要增加日志记录以进行调试。应用程序服务器配置信息、数据库连接信息和架构配置可以在构建文件中设置。

下面是一段可以使用的示例文本文件。注意,此类文件可能包含客户端机密和API机密,因此不应该提交给源码控制。

API_URL=http://localhost:8080
PORT=8080
AUTH_ZERO_CLIENT_ID=fakeClientId
AUTH_ZERO_JWT_TOKEN=someFaketToken.FakedToken.Faked
AUTH_ZERO_URL=https://fake-api.com
REDIS_PORT=redis:6379
SEND_EMAILS=true
SMTP_SERVER=fakeamazoninstance.us-east-1.amazonaws.com
SMTP_USERNAME=fakeUsername
SMTP_PASSWORD=FakePassword
SMTP_PORT=587
TOKEN_SECRET="A fake Token Secret"

诸如此类的配置文本文件可以帮助其他开发人员连接到第三方服务,并有助于组织存储客户端机密信息的位置。

小型构建通常是可以由CI服务器快速运行的构建,通常包括编译步骤以及所有单元测试的运行。小型构建可以通过运行阶段构建来优化,这个内容将在2.2.7节进行讨论。

大型构建实质上是在一个大的构建中运行所有构建任务的构建。进行大型构建的缺点是,开发人员在运行构建的过程中常常受阻。如果软件构建需要很长时间来运行,那么许多开发人员将完全避免运行该构建。快速运行的较小版本构建鼓励开发人员在版本控制系统上不断签入他们的更改,有助于保持代码库的良好状态。

CI构建实践就像上台阶,每个环节都建立在先前的环节之上。正如第3章将要讨论的那样,CI构建过程中的每个步骤都很重要,它们可以确保代码库处于良好状态。

1.私有构建

开发人员在提交代码至存储库之前应当运行私有构建。

使用Git的开发者会话示例如下。

检查将从存储库更改的代码:

进入受版本控制的文件夹;

git checkout -b new_branch。

更改代码:

编辑myFile.go。

从存储库中获取最新的系统更改:

git pull。

运行一个在本地计算机上运行所有单元测试以及可能的集成测试的构建。

将代码更改提交到存储库。

CI构建应自动触发构建并运行存储库中的任何测试。

CI构建还应该执行其他任务,例如在需要时报告和调用其他服务。

2.CI服务器的使用

CI服务器应按指定的时间间隔轮询版本控制存储库系统(如GitHub)中的更改,或通过Webhook进行配置以触发软件构建。CI构建应按预先约定的原则执行某些操作——如果需要,可每小时或每天执行一次。开发人员应该确定一个静止期(quiet period),在此期间不对项目运行任何的集成构建。CI服务器应支持不同的构建脚本工具,如Rake、Make、NPM或Ant。CI服务器应将电子邮件发送给有关各方,并展示构建历史。

CI服务器应显示可通过Web访问的仪表盘(dashboard),以便所有相关方可以在必要时查看集成构建信息。Jenkins、Travis CI和CircleCI都具有可通过Web访问的仪表盘。CI服务器应为不同项目支持多个版本控制系统,例如SVN、Git和Mercurial。

3.手动集成构建

如果存在长期运行的功能(很难在CI服务器上运行),则手动运行集成构建是一种减少集成构建错误的方法,但是请谨慎使用这种方法。例如,可以指定一台不用于执行手动集成任务的机器;尽管有了云,但现在比以往任何时候都更容易按需启动服务器实例。

4.运行快速构建

通过增加计算资源来尽快运行软件构建。将运行较慢的测试(如系统级测试)转移到二级构建或每夜构建中。将代码检查工作转移到第三方服务。以代码覆盖率分析为例,可以使用以下第三方服务:

Codecov;

Coveralls;

Code Climate;

Codacy。

运行分阶段构建(staged build)也可以促进快速构建。第一次构建可以编译并运行所有单元测试。第二次构建可以运行所有集成测试和系统级测试。可根据需要分为任意数量的阶段以进行快速构建。可以认为第一次构建应该是最快的,因为这是开发人员将代码签入代码库时使用的主要构建。

本章介绍了持续集成的基础概念,并介绍了开发团队成功部署CI服务器的技术。本章研究了脚本工具和构建工具,讨论了什么是软件构建、创建构建脚本时应遵循的最佳实践,以及一些测试概念(如代码覆盖率)。第3章是对CI的自然扩展,将详细介绍部署流水线、配置管理、部署脚本编写和部署生态系统。

1.什么是软件构建?

2.分阶段构建的含义是什么?

3.说出一些脚本工具的名称。

4.为什么要遵循命名约定和目录结构?

5.CI的价值是什么?

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e58472”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


相关图书

有限元基础与COMSOL案例分析
有限元基础与COMSOL案例分析
程序员的README
程序员的README
现代控制系统(第14版)
现代控制系统(第14版)
现代软件工程:如何高效构建软件
现代软件工程:如何高效构建软件
GitLab CI/CD 从入门到实战
GitLab CI/CD 从入门到实战
科学知识图谱:工具、方法与应用
科学知识图谱:工具、方法与应用

相关文章

相关课程