在软件开发的整个过程中,设计、编码、测试固然重要,但是也不能忽略了“最后一公里”,即部署和发布的过程。我们的目标是快速、可靠地发布软件,软件真正产生价值是在交付给用户的时候,传统、手工的部署方式不仅效率低,而且过程中容易出错。持续交付可以帮我们解决这一问题。持续交付倡导的是一个自动化的构建、部署、测试和发布的流水线,整个过程都需要规范管理起来。
1 配置管理
1.1 使用版本控制
1)对所有内容进行版本控制 所有软件相关的产物都需要进行版本控制,不仅是源代码,还应当包括所有测试代码、文档、数据库脚本、配置信息等,否则,本书提到的提高软件交付质效的实践:持续集成、自动化测试、一键式部署都是无法实践的。
2)频繁提交代码到主干 提交地越频繁,合并的工作量越小,发生的冲突越容易解决,越容易进行评估和测试,出错的风险也越低。
3)使用意义明显的提交注释 为了方便对代码进行回溯,提高代码的可读性,每次提交时都应该有准确的注释来说明这段代码是为了新增某个功能或者解决某个问题的。还有一个建议是,每次提交应该只覆盖一个功能或者只解决一个问题,通常有的开发同学在一次提交中包含了两个以上的功能,但是注释却只说明了其中一个的作用,这样也是不合理的。
1.2 依赖管理 包括外部库文件和组件
1.3 软件配置管理 确保配置项在多个应用、多个组件以及多项技术中的管理保持一致性。每个人都应该能够非常容易地看到当前软件的某个特定版本部署到各种环境上的具体配置信息,同时使用冒烟测试来诊断依赖于配置项的相关功能是否都能正常工作。
1.4 环境管理 通过一个全自动的过程来创建环境,同时,环境的变更过程也需要进行管理。
2 持续集成
没有持续集成的话,直到验证前,应用程序可能一直都处于无法工作的状态,而有了持续集成之后,应用程序就应该是时刻处于可工作状态的了。持续集成创建了一个快速的反馈环,使你能尽早地发现问题,而发现问题越早,修复成本越低。从而确保我们作为一个团队所开发的软件是可以正常工作的。它主要关注于代码是否可以编译成功以及是否可以通过单元测试和验收测试。持续集成系统的输出通常作为手工测试流程和后续发布流程的输入。持续集成是一种实践,而不是一个工具。
2.1 准备工作
在开始做持续集成之前,需要做三件事:版本控制、自动化构建、团队共识。
版本控制:与项目相关的所有内容都必须提交到一个版本控制库中,包括产品代码、测试代码、数据库脚本、构建与部署脚本,以及所有用于创建、安装、运行和测试该应用程序的东西。
自动化构建:人和计算机都能通过命令行自动执行应用的构建、测试以及部署过程。
团队共识:团队成员都接收以小步增量的方式频繁地将修改后的代码提交到主干,并一致认同“修复破坏应用程序的任意修改是最高优先级的任务”的准则。
2.2 前提条件
持续集成的前提条件:频繁提交、创建全面的自动化测试套件、保持较短的构建和测试过程、管理开发工作区。
2.3 使用持续集成软件
持续集成工作流以规定的时间间隔对版本控制系统进行轮询,一旦发现版本库有任何变化,它就会将项目的一个副本签出到服务器或构件代理机器的某个目录中,然后运行你指定的命令。典型情况下,这些命令会构件你的应用程序,并运行相关的自动化测试。最后提供展现这个流程运行结果的视图,通知你构建和测试成功与否,让你可以找到测试报告,拿到生成的安装文件等。
2.4 必不可少的实践
构建失败之后不要提交新代码、
提交前在本地运行所有的提交测试,或者让持续集成服务器完成此事
等提交测试通过后再继续工作
回家之前,构建必须处于成功状态
时刻准备着回滚到前一个版本
在回滚之前要规定一个修复时间
不要将失败的测试注释掉
为自己导致的问题负责
测试驱动的开发
2.5 推荐的实践
极限编程开发实践
若违背架构原则,就让构建失败
若测试运行变慢,就让构建失败
若有编译警告或代码风格的问题,就让测试失败
2.6 分布式团队 使用共享的版本控制系统和持续集成系统
2.7 分布式版本控制系统
3 测试策略的实现
测试策略的设计主要是识别和评估项目风险的优先级,以及决定采用哪些行动来缓解风险的一个过程。好的测试策略会带来很多积极作用。测试会建立我们的信心,使我们相信软件可按预期正常运行。也就是说,软件的缺陷较少,技术支持所需的成本较低,客户认可度较高。测试还为开发流程提供了一种约束机制,鼓励团队采用一些好的开发实践。一个全面的自动化测试套件甚至可以提供最完整和最及时的应用软件说明文档,这个文档不仅是说明系统应该如何运行的需求规范,还能证明这个软件系统的确是按照需求来运行的。
3.1 测试的分类
1)业务导向且支持开发过程的测试:功能测试/验收测试 验收测试确保用户故事的验收条件得到满足。系统的验收测试应该运行在类生产环境中。自动化验收测试有很大价值:加快反馈速度,让测试人员集中精力做探索测试和高价值的活动,作为回归测试的一部分。一旦对同一个测试重复做过多次手工操作,并且你确信不会花太多时间来维护这个测试时,就要把它自动化。需要写的最重要的自动化测试是那些对Happy Path的测试。每个需求或用户故事都应该有对Happy Path的自动化验收测试,而且应至少有一个。这些测试应该被每位开发人员当做冒烟测试来使用,从而能够为“是否破坏了已有的功能”提供快速反馈。当你有时间写更多的自动化测试时,很难在Happy Path和Sad Path之间进行选择。如果你的应用程序比较稳定,那么Alternate Path应该是你的首选,因为它们是用户所定义的场景。如果你的应用有较多的缺陷并且经常崩溃的话,那么战略性地应用对Sad Path的测试会帮助你识别那些问题域并修复它们。而且自动化可以保证应用程序保持在稳定状态。
2)技术导向且支持开发过程的测试:单元测试、组件测试和部署测试
3)业务导向且评价项目的测试:演示、易用性测试、探索性测试
4)技术导向且评价项目的测试:非功能验收测试,包括容量测试、安全性测试等
3.2 显示中的情况与应对策略
1)新项目 新项目通过建立一些相对简单的基本规则,并创建一些相对简单的测试基础设施,就可以很顺利地开始你的持续集成之旅。相对于项目开发几个迭代后再写验收测试来说,在项目开始就采用这样的流程是比较容易的。从一开始就使用了自动化验收测试的代码库一般总是有更好的封装、更清晰的表达、更清楚的职责分离和更多的代码重用。
2)项目进行中 引入自动化测试最好的方式是选择应用程序中那些最常见、最重要且高价值的用例为起点。这就需要与客户沟通,以便清楚地识别真正的业务价值是什么,然后使用测试来做回归,以防止功能被破坏。基于这些沟通,把Happy Path的测试自动化,用于覆盖高价值的场景。另外,让这些测试尽可能覆盖更多的选项也是有用的,即让测试覆盖的范围稍稍宽于通常的用户故事级别的验收测试,比如尽可能多填一些字段,多单击一些按钮来满足测试需求。
3)遗留系统 针对高价值的功能创建自动化测试,并将这些覆盖核心功能的测试作为冒烟测试。把自动化测试分成不同的层级。第一级应该是那些非常简单且运行较快的测试,第二级是测试某个具体用户故事的关键功能。针对用户故事的Alternate Path和异常条件也可以写验收测试。
4)集成测试 假如你的应用程序需要通过一系列不同的协议与各种外部系统进行交互,或者它由很多松散耦合的模块组成,而模块之间还有很复杂的交互操作的话,集成测试就非常重要了。尽可能模拟异常情况,并用它们来测试你的应用程序,以便确保应用可以处理这类情况。
3.3 流程
迭代开始时组织相关人员一起编写验收条件,然后再写代码让这些验收条件变成可执行的测试。与开发完用户故事之后再沟通相比,这会大大减少开发人员和测试人员之间的反馈循环,有助于减少遗漏功能的几率,并有助于减少缺陷。
4 部署流水线
若从开发直到在类生产环境上部署之间需要很长时间,就会导致软件无法部署,而若开发团队与测试、运维人员之间的反馈周期太长,就会使软件存在很多缺陷。现在,我们通常能通过一键式方式把软件的某个版本部署好,甚至可以将其一键式部署到生产环境中,这样就建立了一个非常有效的反馈环——由于很容易将应用程序部署到测试环境中,所以团队可以同时得到软件功能和部署流程两个方面的快速反馈。
4.1 什么是部署流水线
从某种抽象层次上讲,部署流水线是指软件从版本控制库到用户手中这一过程的自动化表现形式。流水线的输入是版本控制中的某个具体版本,每次变更都会生成一次构建,这个构建会经过一系列的测试阶段,每个测试阶段都从不同的角度评估这个构建版本,且和持续集成一样,它的起点是向版本控制库的每一次提交。随着某个构建逐步通过每个测试阶段,我们对它的信心也在不断提高。在每个阶段上花在环境方面的资源也在不断增加,即越往后的阶段,其环境与生产环境越相似,其目的就是在这个过程中尽早发现那些不满足发布条件的构建版本,并尽快将失败根源反馈给团队。一般来说,只要某个构建使无论是这一流程中的哪个阶段失败了,它都不会进入下一个阶段。
使用这种模式的话,有些非常重要的积极影响。首先,它可以有效地阻止那些没有经过充分测试或不满足功能需求的版本进入生产环境,也能避免回归缺陷,尤其是对于那些需要紧急修复并部署到生产环境的情况。其次,当部署和产品发布都被自动化之后,这些活动就变成快速、可重复且可靠的了。
最基本的部署流水线 这个流程的起点是开发人员向版本控制库提交代码。第一个阶段会编译代码,运行单元测试,执行代码分析,创建软件二进制包。第二个阶段通常由运行时间较长的自动化验收测试组成。
部署流水线的组成:
提交阶段是从技术角度上断言整个系统是可以工作的。这个阶段会进行编译,运行一套自动化测试(主要是单元级别的测试),并进行代码分析
自动化验收测试阶段是从功能和非功能角度上断言整个系统是可以工作的,即从系统行为上看,它满足用户的需要并且符合客户的需求规范。
手工测试阶段用于断言系统是可用的,满足了它的系统要求,试图发现那些自动化测试未能捕获的缺陷,并验证系统是否为用户提供了价值。这一阶段通常包括探索性测试、集成环境上的测试以及用户验收测试。
发布阶段旨在将软件交付给用户,既可能是以套装软件的形式,也可能是直接将其部署到生产环境,或试运行环境。
4.2 部署流水线的相关实践
1)只生成一次二进制包 这一原则的一个必然结果是能够在任意环境上部署这些二进制包,这样会引导你正确地管理配置信息,促使你使用结构良好的构建系统
2)对不同环境采用同一部署方式 如果对于不同的环境,其部署脚本也不相同的话,你就无法知道某个测试过的脚本是否在上线部署时还能正常工作。相反,如果使用同一个脚本在所有的环境上进行部署,那么当在某个环境上部署失败时,就可以确定其原因一定来自以下三个方面:与该环境相关的配置文件中,某项配置有问题;基础设施或应用程序所依赖的某个服务有问题;环境本身的配置有问题
3)对部署进行冒烟测试 如果应用程序不能运行,这个冒烟测试应该能够告诉你一些最基本的诊断提示,比如应用程序无法运行是否是因为其依赖的外部服务无法正常工作
4)向生产环境的副本中部署 为了对系统上线充满信心,你要尽可能在与生产环境相似的环境中进行测试和持续集成。你要确保:基础设施是相同的,比如网络拓扑和防火墙的配置等;操作系统的配置(包括补丁版本)都是相同的;应用程序所用的软件栈是相同的;应用程序的数据处于一个已知且有效的状态
5)每次变更都要立即在流水线中传递
6)只要有环节失败,就停止整个流水线 每次提交代码到版本控制系统中后,都能够构建成功并通过所有的测试。假如在某个环境上的某次部署失败了,整个团队就要对这次失败负责,应该停下手头的工作,把它修复后再做其他事情。
4.3 提交阶段 每次提交都生成部署流水线的一个新实例。如果提交阶段的测试通过了,这个版本就被视为一个候选发布版本。一般来说,提交阶段包含以下步骤:编译代码、运行一套提交测试、为后续阶段创建二进制包、执行代码分析来检查代码的健康状况、为后续阶段做准备工作,比如准备一下后续测试所用的数据库。如果前面这些任务都成功了,提交阶段的最后一步就是生成二进制包,用于后续阶段的部署。
4.4 自动化验收测试之门 全面的提交测试套件对于发现许多种错误来说,是非常优秀的试金石。然而,有很多类型的错误是它无法捕获的。如果没有在类生产环境上执行验收测试,我们就根本不知道该应用程序是否符合了客户规范,也不知道它在现实世界中是否能够部署并运行。如果想在这些方面得到及时反馈的话,就必须在持续集成流程中引入更多测试并不断对系统各方面进行演练。验收测试阶段的目标是断言应用城促交付了客户期望的价值,并满足了验收条件。
4.5 后续的测试阶段
1)手工测试 在迭代开发过程中,验收测试之后一定会有一些手工的探索性测试、易用性测试和演示。自动化验收测试使测试人员节省出更多的时间做那些高价值的活动,而不是测试脚本的人力执行器
2)非功能测试 每个系统都有很多非功能需求。比如,几乎每个系统都有容量和安全性方面的要求,或者必须遵守服务水平协议等。通常应该用某些自动化测试衡量应用程序是否满足这些需求。
4.6 发布准备 每次向生产环境发布时都有业务风险。缓解这类风险非常简单,只要把这个发布环节视为部署流水线的一个自然结果就行。
1)让参与项目交付过程的人共同创建并维护一个发布计划
2)自动部署与发布
3)在类生产环境中经常做发布流程演练
4)变更的撤销
4.7 实现一个部署流水线
1)对价值流进行建模并创建简单的可工作框架
2)构建和部署过程的自动化
3)自动化单元测试和代码分析
4)自动化验收测试
4.8 度量 对软件交付过程来说,最重要的全局度量指标就是周期时间。它指的是从决定要做某个特性开始,直到把这个特性交付给用户的这段时间。一定要创建一个聚合所有信息,并且人脑可以直接通过其无比的模式匹配能力识别流程或代码库中问题的可视化报告。部署流水线可以帮助消除流程中的低效环节,这样可让反馈周期更短并更有效。
5 构建与部署的脚本化
5.1 原则与实践
1)为部署流水线的每个阶段创建脚本‘
2)使用恰当的技术部署应用程序
3)使用同样的脚本向所有环境部署
4)使用操作系统自带的包管理工具
5)确保部署流程是幂等的
6)部署系统的增量式演进
5.2 部署脚本化
1)多层的部署和测试 底层是操作系统,然后是中间件和应用程序所依赖的其他软件。一旦这两层准备好了,就需要对其进行一些具体配置,为应用程序的部署做准备。只有这些都做完了,我们才能开始部署软件,这包括可部署的二进制包、所需要的服务或守护进程,以及其相关配置。
2)测试环境配置 任何一个层级的部署出错,都可能导致应用程序无法正常运行。所以,当准备每一层级时,都要对其进行测试。如果发现问题,就要让环境配置流程快速失败,而测试结果也应该给出清晰指示,指出错误出现在哪里。
5.3 小贴士
1)总是使用相对路径
2)消除手工步骤
3)从二进制包到版本控制库的内建可追溯性
4)不要把二进制包作为构建的一部分放到版本控制库中
5)test不应该让构建失败
6)用集成冒烟测试来限制应用程序
6 提交阶段
当某人向版本控制库的主干上提交了一次变更后,持续集成服务器会发现这次变更,并将代码签出,执行一系列的任务,包括:
编译(如果需要的话),并在集成后的源代码上运行提交测试;
创建能部署在所有环境中的二进制包(如果使用需要编译的语言,则包括编译和组装);
执行必要的分析,检查代码库的健康状况;
创建部署流水线的后续阶段需要使用的其他产物(比如数据库迁移或测试数据)。
6.1 提交阶段的原则和实践
1)提供快速有用的反馈
提交测试的失败通常是由以下三个原因引起的:由于语法错误导致编译失败、由于语义错误导致一个或多个测试失败、由于应用程序的配置或环境方面(包括操作系统本身)的问题引起。
引入错误后,越早发现它,就越容易修复它。因为引入错误的人对其上下文的印象还比较深,而且找到错误原因的方法也比较简单。
2)何时令提交阶段失败
除了成功和失败两种状态,提交阶段结束后,应该提供更丰富的信息,比如关于代码覆盖率和其它度量项的一些图表。
3)精心对待提交阶段
随着项目的进行,要不断努力地改进提交阶段脚本的质量、设计和性能。一个高效、快速、可靠的提交阶段是提高团队生产效率的关键,所以只要花点儿时间和精力在这上面,让它处于良好的工作状态,就会很快收回这些投入成本。
4)让开发人员也拥有所有权
如果必要的话,即使是很普通的变更(比如增加新的库文件和配置文件等)也都应该由一起工作的开发人员和运维人员来执行。这类活动不应该由构建专家完成,除非是在项目初期团队刚开始建立构建脚本时。
5)在超大项目团队中制定一个构建负责人
如果构建失败,构建负责人要知会当事人并礼貌地(如果时间太长的话,不礼貌也没问题)提醒他们为团队修复失败的构建,否则就将他们的修改回滚。这个角色能起作用的另一种情况是,当团队刚开始接触持续集成时。在这样的团队中,构建纪律还没有建立起来,有个人能不断提醒大家,会令事情走向正轨。
6.2 提交阶段的结果
1)交付团队的某个人提交了一次修改
2)持续集成服务器运行提交阶段
3)成功结束后,二进制包和所有报告和元数据都被保存到制品库中
4)持续集成服务器从制品库中获取提交阶段生成的二进制包,并将其部署到一个类生产测试环境中
5)持续集成服务器使用提交阶段生成的二进制包执行验收测试
6)成功完成后,该候选发布版本被标记为“已成功通过验收测试”
7)测试人员拿到已通过验收测试的所有构建的列表,并通过单击一个按钮将其部署到手工测试环境中
8)测试人员执行手工测试
9)一旦手工测试也通过了,测试人员会更新这个候选发布版本的状态,指示它已经通过手工测试了
10)持续集成服务器从制品库中拿到通过验收测试(根据部署流水线的配置,也可能是手工测试)的最新候选发布版本,将其部署到生产测试环境
11)对这个候选发布版本进行容量测试
12)如果成功了,将这个候选版本的状态更新为“已通过容量测试”
13)如果部署流水线中还有后续阶段的话,一直重复这种模式
14)一旦这个候选发布版本通过了所有相关阶段,把它标记为“可以发布”,并且任何被授权的人都能将其发布,通常是由质量保证人员和运维人员共同批准
15)一旦发布以后,将其标记为“已发布”
6.3 提交测试套件的原则与实践
1)避免用户界面
2)使用依赖注入
3)避免使用数据库
4)在单元测试中避免异步
5)使用测试替身
6)最少化测试中的状态
7)时间的伪装
8)蛮力
7 自动化验收测试
一旦正确实施自动化验收测试,你就是在测试应用程序的业务验收条件,即验证应用程序是否为用户提供了有价值的功能。作为一个整体,验收测试套件既验证了应用程序是否交付了用户期望的业务价值,又能防止回归问题或缺陷破坏了应用程序的原有功能。
7.1 为什么验收测试是至关重要的
单元测试和组件测试都不能以实际运行来证明软件能为客户提供他们所期望的业务价值
应用程序出现的紧急行为以及由架构问题或环境及配置问题造成的其他类型的bug很难通过手工测试发现
手工验收测试的成本昂贵
原计划预留的时间并不足以修复手工验收测试中发现的缺陷
1)如何创建可维护的验收测试套件 关注验收条件、对验收测试进行分层(验收条件、测试实现层、应用程序驱动层)、使用状态管理、超时处理和测试替身的方式
2)GUI上的测试的问题:界面变化速度很快、场景的设置复杂、拿到测试结果很难,以及不可测的GUI技术
7.2 创建验收测试
1)分析人员和测试人员的角色
分析人员与客户一起工作,识别需求,并排定优先级;分析人员与开发一起工作,确保开发人员能从用户的角度很好地理解应用程序,确保那些用户故事真正交付了它们应有的业务价值;分析人员与测试人员一起工作,确保验收条件已被合理阐明,并且开发出来的功能满足这些验收条件,交付了期望的价值。
测试人员的角色就是确保交付团队的每个人(包括客户)都了解并理解正在开发的软件的当前质量和生产准备情况。为了做到这一点,他们要与客户和业务分析师一起工作,为用户故事或需求定义验收条件,与开发人员一起工作,编写自动化验收测试,他们还要执行手工测试活动,比如探索性测试、手工验收测试和演示。
2)迭代开发项目中的分析工作
在迭代交付方法中,分析人员会花大量时间定义验收条件。最开始,分析人员会与测试人员和客户紧密合作,定义验收条件。在这个阶段,鼓励分析人员和测试人员协作不仅对双方都有利,并且能使流程更加有效。分析师会有所收获,因为测试人员会根据他们的经验提供一些信息,比如哪些事情可能或应该用户定义用具故事是否做完了。而测试人员在测试这些需求之前,就能获得对这些需求本质的理解。
一旦验收条件定义完成,在开始实现这个需求之前,分析人员、测试人员应该和将要实现这个需求的开发人员碰一下头儿。确保实现需求的每一方都能很好地理解需求以及他们在交付过程中的角色。这种方法可以避免分析人员创建那种难以实现或测试的象牙塔式需求,也避免测试人员由于自己对系统的错误理解,把正常的系统行为当成缺陷写在报告里,还可以避免开发人员实现一些不相干的、客户并不想要的功能。
在实现需求时,如果开发人员发现对某个地方不是非常理解(或者发现了一个问题,或找到更高效的方法可以解决需求问题),就可以去问分析人员。
当开发人员认为工作已经完成时,通常是指所有相关的单元测试和组件测试都已经通过了,验收测试也全部实现,并证明系统满足需求。此时,他们就可以向分析人员、测试人员和客户进行演示。
3)将验收条件变成可执行的规格说明书
拿这些写好的验收条件直接在应用程序之上运行,来验证它是否满足规格说明;这种可执行的规格说明不会过时;它们验证应用程序的确向用户交付了价值。这种创建可执行规格说明的方法是行为驱动设计的本质
7.3 实现验收测试
1)验收测试中的状态
建立一个已知的起始状态,并让测试对复杂状态的依赖最小化
2)过程边界、封装和测试
3)管理异步与超时问题
4)使用测试替身对象
7.4 验收测试阶段
提交测试一旦成功完成,就应该开始在通过提交测试的软件版本上运行验收测试套件。令验收测试失败的构建版本不能被部署。
1)确保验收测试一直处于通过状态
整个交付团队应该为保持验收测试通过负责。当某个验收测试失败时,团队要停下来立即评估问题。至关重要的是,为了保证验收测试一直可以工作,并使开发人员关注应用程序的行为,要让整个交付团队对验收测试拥有所有权和维护权权,而不仅仅是测试团队的事情。尽早修复失败的验收测试是至关重要的,否则测试套件就没有贡献真正的价值。其中最重要的一步就是让失败可视化。
2)部署测试
在和生产环境一直的测试环境中运行验收测试,用于断言我们配置的环境与期望一致,并且系统中各种组件中的通信也是正常的。
7.5 验收测试的性能
引入问题的时间点和发现问题的时间点之间的时间越长,发现问题根源并修复它的难度越大。有一系列的技术能用来缩短从验收测试阶段得到运行结果的时间,从而提高团队的整体效率。
1)重构通用任务
2)共享昂贵资源
3)并行测试
4)使用计算网络
7.6 小结
采纳验收测试条件驱动的测试代表了更先进的理念,因为它:
为系统进行大范围修改提供了一个保护网
通过全面的自动回归测试极大地提高了质量
无论什么时候出现缺陷,都能提供快速、可靠的反馈,以便可以立即修复
让测试人员有更多的时间和精力去思考测试策略、开发可执行的规格说明,以及执行探索性测试和易用性测试
缩短周期时间,使持续部署成为可能
8 非功能需求的测试
性能是对处理单一事务所花时间的一种度量,既可以单独衡量,也可以在一定的负载下衡量。吞吐量是系统在一定时间内处理事务的数量,通常它受限于系统中的某个瓶颈。在一定的工作负载下,当每个单独请求的响应时间维持在可接受的范围内时,该系统所能承担的最大吞吐量被称为它的容量。
在项目开始就识别出哪些是重要的非功能需求,这一点至关重要。然后,团队就要找到某种方法来衡量这些非功能需求,并在交付时间表中考虑什么时候做这些测试,把它们放在部署流水线的哪个位置。
8.1 非功能需求的管理
在交付过程的后期才发现应用程序因基本的安全漏洞或很差的性能而导致项目无法验收,这种常见现象会导致项目推迟交付甚至被取消。在项目一开始,交付过程中的每个人(包括开发人员、运维人员、测试人员和客户)都需要思考一下应用程序的非功能需求,以及它们对系统架构、项目时间表、测试策略和总成本的影响。
1)创建一些具体任务来管理非功能需求 以用户故事的方式定量描述系统在这方面的期望是合理的,并且要定义足够多的细节,这样就可以做成本与收益的分析,并依此对它们进行优先级的排定了。
2)如果有必要,向其他功能需求中加入非功能需求的验收条件
8.2 如何为容量编程
容量测试阶段的关键在于,它要告诉我们是否存在问题,以便我们可以修复它。不要妄自猜测,而要先进行度量。
过早且过分地关注应用程序的容量优化是低效且昂贵的。而且,最终交付的应用系统也很少是高性能的。更糟糕的是,它甚至可能让项目无法交付。
1)为应用程序决定一种架构。通常要特别注意进程、网络边界和I/O
2)了解并使用正确的模式,避免使用那些影响系统容量和稳定性的反模式
3)除了采用适当模式以外,还要确保团队在已经明确的应用架构下进行开发,不要为了容量做无谓的优化。鼓励写清晰且简单的代码,而不是深奥难以理解的代码。在没有明确测试结果表明有容量问题时,坚决不能在代码可读性上作出让步
4)注意在数据结构和算法方面的选择,确保它们的属性与应用程序相吻合。比如,只需要O(1)的性能,就不要用一个O(n)的算法
5)处理线程时要特别小心。要能达到这一点,关键之一就是保持应用程序的核心是单线程的
6)创建一些自动化测试来断言所期望的容量级别。当这些测试失败时,用它们作为向导来修复这些问题
7)使用调测工具主要关注测试中发现的问题,并修复它,不要使用“让它越快越好”这类策略
8)只要有可能,就使用真实的容量数据来做度量。生产环境是唯一真实度量的来源。使用这样的数据,并分析这些数据到底说明了什么。特别要注意系统的用户数,他们的行为模式以及生产环境中的数据量
8.3 容量度量
扩展性测试 随着服务器数、服务或线程的增加,服务或线程的增加,单个请求的响应时间和并发用户数的支持会如何变化
持久性测试 这是要长时间运行应用程序,通过一段时间的操作,看是否有性能上的变化。这类测试能捕获内存泄漏或稳定性问题
吞吐量测试 系统每秒能处理多少事务、消息或页面点击
负载测试 当系统负载增加到类似生产环境大小或超过它时,系统的容量如何?这也许是最典型的容量测试
容量测试的一个重要方面是能够为给定的应用程序模拟真实的使用场景。另外一种方法是找出系统中具体操作的技术基准。目标明确的基准式容量测试对于代码中某个具体问题的防范或局部代码优化是非常有用的。如果对应用程序来说,性能或吞吐量是一个重要指标的话,我们就需要用一些测试来断言系统能够满足业务需求,而不是通过技术经验来猜测某个特定组件的吞吐量应该是多少。在容量测试策略中还要包含基于场景的测试,这一点非常关键。
如何定义容量测试的成功与失败
首先,把目标设定为得到稳定、可重现的结果。只要有可能的话,为容量测试专门准备一个环境,用于度量容量。然后,一旦某个测试通过了最低验收标准,就把验收标准提高一点儿,调整该测试的成功门槛。为了使测试更好用,而不只是性能度量,每个测试都必须体现一个具体的场景,并且只有达到某个标准门槛时,才能认为该测试通过了。
8.4 容量测试环境
理想情况下,系统容量的绝对度量应该在一个尽可能与生产环境相似的环境上执行。假如对某应用程序来说,容量或性能是一个非常关键的问题,那么就 一定要有所投入,为该系统的核心部分准备一个生产环境的副本。使用相同的软硬件规格要求,遵循我们关于如何管理配置信息的建议,以确保每个环境中都使用相同的配置文件,包括网络配置、中间件及操作系统的配置。然而,大多数项目的情况应该在这两者之间,即在一个与生产环境尽可能相似的环境中运行容量测试,它会让那些严重的容量问题突显出来。另外,也不要依据硬件的某种特定参数对应用程序的扩展性作出线性推论,复杂系统的行为很少是这种线性相关的。对于那些需要部署到服务器集群中的应用程序来说,一个既可以降低环境成本又能提供适当准确度量的策略就是,仅复制一小部分的服务器,而不是整个集群。
8.5 自动化容量测试
如果想在部署流水线中增加容量测试的话,就应该创建一个自动化容量测试套件,并且每次对系统进行修改之后,一旦通过了提交测试和验收测试(可选),就应该执行容量测试。
容量测试应该达到以下几点目标:
测试具体的现实场景,这样就不会因为测试太抽象而错过真实应用场景中那些重要的bug
预先设定成功的门槛,这样就能判定容量测试是否通过了
尽可能让测试运行时间短一些,从而保证容量测试在适当时间内完成
在变更面前要更健壮一些,从而避免因对应用程序的频繁修改而不断返工
组合成大规模的复杂场景,这样就可以模拟现实世界中的用户使用模式
是可重复的,并且既能串行执行,也能并行执行,以便这些测试既可以做负载测试,也可以做持久性测试
8.6 将容量测试加入到部署流水线中
当明确规定了成功条件的自动化容量测试成功通过以后,就证明已满足容量需求。通过这种方式,可以知道我们避免对容量问题的过分设计。和其他测试一样,容量测试阶段也要从部署准备开始,然后核实环境和应用程序都一杯正确配置和部署,并完成冒烟测试之后才是容量测试的执行。
8.7 容量测试系统的附加价值
重现生产环境中发现的复杂缺陷
探测并调试内存泄漏
持久性测试
评估垃圾回收的影响
垃圾回收的调优
应用程序参数的调优
第三方应用程序配置的调优,比如操作系统、应用程序服务器和数据库配置
模拟非正常的、最糟糕情况的场景
评估一些复杂问题的不同解决方案
模拟集成失败的情况
度量应用程序在不同硬件配置下的可扩展性
与外部系统进行交互的负载测试,即使容量测试的初衷是与桩替身接口打交道
复杂部署的回滚演练
有选择地使系统的部分或全部瘫痪,从而评估服务的优雅降级
在短期可用的生产硬件上执行真实世界的容量基准,以便能计算出长期且低配的容量测试环境中更准确的扩展因素
9 应用程序的部署与发布
启动自动部署系统,将要部署的软件版本和目标环境的名称告诉它,并点击“开始”就行了。所有后续部署和发布都要使用同样的流程。这样,就能看到每个环境中究竟运行的是哪个版本的应用程序,谁授权部署了这个版本,从上次部署之后应用程序到底有哪些修改。
9.1 创建发布策略
明确负责人、配置管理策略、监控需求、灾难恢复计划、生产环境的数量大小及容量计划、修复和升级策略
1)发布计划
第一次部署应用程序部署步骤、如何进行冒烟测试、升级和撤销步骤、备份和恢复步骤、重新启动和重新部署步骤、迁移步骤
2)发布产品
考虑收费模式、使用许可策略、第三方技术的版权问题、打包、市场活动所需要的材料、产品文档、安装包、销售和售后支持团队的准备
9.2 应用程序的部署和晋级
1)首次部署
在第一个迭代结束时,要实现部署流水线的提交阶段、一个用于部署的类生产环境、通过一个自动化过程获取在提交阶段中生成的二进制包并将其部署到这个类生产环境中、简单冒烟测试用于验证本次部署是正确的
类生产环境的特点:它的操作系统与生产环境一致、安装的软件与生产环境一致、用与管理生产环境相同的方式对这种环境进行管理、对于客户自行安装的软件要基于客户硬件环境的调研结果
2)对发布过程进行建模并让构建晋级
自动化部署机制使构建版本的晋级变成了一件非常简单的事,只需要选择某个发布候选版本并把它部署到正确的环境中即可。每个需要部署应用程序的人都能用这种自动化部署机制,而不需要了解部署本身相关的任何技术知识。
3)配置的晋级
用冒烟测试来验证配置信息的指向是正确的
4)联合环境
在集成测试环境运行冒烟测试来作为验收测试
5)部署到试运行环境
在用户使用应用程序之前,应该在试运行环境上执行一些最终测试。
9.3 部署回滚和零停机发布
当指定发布回滚计划时,需要遵循两个通用原则。首先,在发布之前,确保生产系统的状态(包括数据库和保存在文件系统中的状态)已备份。其次,在每次发布之前都练习一下回滚计划,包括从备份中恢复或把数据库备份迁移回来,确保这个回滚计划可以正常工作。
1)通过重新部署原有的正常版本来进行回滚
2)零停机发布 将用户从一个版本几乎瞬间转移到另一个版本上
3)蓝绿部署 系统的用户被引导到当前正在作为生产环境的绿环境中,把新版本发布到蓝环境中,在蓝环境上运行冒烟测试,当一切准备就绪以后只要修改一下路由配置,将用户从绿环境导向蓝环境即可。
4)金丝雀发布 把应用程序的某个版本部署到生产环境中的部分服务器中,从而快速得到反馈
9.4 紧急修复
任何情况下,都不能破坏流程。紧急修复版本也要走同样的构建、部署、测试和发布流程,与其他代码变更没什么区别。同时要考虑这个缺陷带来的影响程度。
9.5 持续部署
发布越频繁,两次发布版本之间的差异就会越少,发布的风险越低。同时持续部署迫使创建完整的自动化构建、部署、测试和发布流程。
9.6 小贴士和窍门
1)真正执行部署操作的人应该参与部署过程的创建
2)记录部署活动
3)不要删除旧文件,而是移动到别的位置
4)部署是整个团队的责任
5)服务器应用程序不应该有GUI
6)为新部署留预热期
7)快速失败
8)不要直接对生产环境进行修改
10 基础设施和环境管理
环境:
组成运行环境的服务器的硬件配置信息——比如CPU的类型与数量、内存大小、spindle和NIC等,以及它们互联所需的网络基础设施
应用程序运行所需要的操作系统和中间件(如消息系统、应用服务器和Web服务器,以及数据库服务器等)的配置信息
原则:
使用保存于版本控制库中的配置信息来指定基础设施所处的状态
基础设施应该具有自治特性,即它应该自动地将自己设定为所需状态
通过测试设备和监控手段,应该每时每刻都能掌握基础设施的实时状况
测试环境和生产环境绝大多数是要相似的,才能尽早发现环境方面的问题,以及在向生产环境部署之前对关键活动进行操作演练,从而减少发布风险。利用敏捷技术对基础设施进行有效管理。
10.1 理解运维团队的需要
所有的项目干系人都能打成一个共识,即让发布有价值的软件成为一件低风险的事情。做这件事的最佳方法就是尽可能频繁地发布(即持续交付)。这就能保证在两次发布之间的变更很小。
1)文档与审计
变更管理:任何人在任何时候想修改一下测试环境或生产环境,都必须提出申请,并被审批。在变更申请中,需要包括详细的风险与影响分析,以及出错时的应对方案。这个申请应该在新版本的部署流程启动之前提交,而且不能是在业务人员期望上线前的几个小时才提交。
2)异常事件的告警
运维团队会有自己的系统来监控基础设施和正在运行的应用程序,并希望当系统出现异常状况时收到警报,以便将停机时间最小化。在项目一开始就了解运维团队希望怎样来监控应用程序,并将其列在发布计划之中。
3)保障IT服务持续性的计划
RPO(恢复点目标,即灾难之前丢失多长时间内的数据时可接受的)控制了数据备份和恢复策略,因此数据备份必须足够频繁,并且要小心管理配置信息,才能达到这个RPO。
RTO(恢复时间目标,即服务恢复之前允许的最长停机时间)要额外建立一个生产环境和基础设施的副本,以便当主系统出错时,可以启用这个后备系统。
4)使用运维团队熟悉的技术
在每个项目开始时,开发团队和运维团队就应该坐下来,讨论并决定应用程序的部署应该如何执行。一旦所用技术达成一致,双方可能都需要学习一下这些技术。
10.2 基础设施的建模和管理
每种环境中都有很多种配置信息,所有这些配置信息都应该以自动化方式进行准备和管理。使用能够以自动化方式进行配置和部署的技术是一个必要条件。并且应该把创建和维护基础设施需要的所有内容都进行版本控制,包括操作系统的安装定义项、数据中心自动化工具的配置信息、通用基础设施配置信息、用于管理基础设施的所有脚本。
对于基础设施的变更来讲,部署流水线的工作包括三部分。首先,在对任何基础设施的变更部署到生产环境之前,它应该验证所有的应用程序在这些变更之后也能正常工作,并确保在该新版本的基础设施之上,所有受到影响的应用程序的功能和非功能测试都能成功通过。其次,它应该将这些变更放到运维团队管理的测试和生产环境上。最后,流水线还应该执行部署测试,确保新的基础设施配置已成功部署。
1)基础设施的访问控制
在没有批准的情况下,不允许他人修改基础设施
制定一个对基础设施进行变更的自动化过程
对基础设施进行监控,一旦发生问题,能尽早发现
2)对基础设施进行修改
无论是更新防火墙规则,还是部署服务的新版本,每个变更都应该走同样的变更管理流程
这个流程应该使用一个所有人都需要登录的系统来管理
做过的变更应该详细清楚地记录到日志中,这样便于以后做审计
能够看到对每个环境进行变更的历史,包括部署活动
想做修改的话,首先必须在一个类生产环境中测试通过,而且自动化测试也已经运行完成,以确保这次变更不会破坏该环境中的所有应用程序
对每次修改都应该进行版本控制,并通过自动化流程对基础设施进行变更
需要有一个测试来验证这次变更已经起作用了
10.3 服务器的准备及其配置的管理
一旦安装好操作系统后,就要保证任何配置的修改都是以受控方式进行的。配置管理过程的目标是,保证配置管理是声明式且幂等的,即无论基础设施的初始状态是什么样,一旦执行了配置操作后,基础设施或系统所处的状态就一定是你所期望的状态,即时某个配置项进行了重复设置对配置结果也没有影响。一旦这个系统准备好之后,就能用一个被集中版本控制的配置管理系统对基础设施中的所有测试环境和生产环境进行管理了。之后,就可得到如下收益。
确保所有环境的一致性
很容易准备一个与当前环境配置相同的新环境,比如创建一个与生产环境相同的试运行环境
如果某个机器出现硬件故障,可以用一个全自动化过程配置一个与旧机器完全相同的新机器
10.4 中间件的配置管理
1)管理配置项
2)产品研究
3)考查中间件是如何处理状态的
4)查找用于配置的API
5)使用更好的技术
10.5 基础设施服务的管理
1)网络基础设施配置的每个部分都应该进行版本控制
2)安装一个好用的网络监控系统。保证当网络连接被破坏时你就会得到通知,而且监控应用程序所使用的每个路由的每个端口。
3)每次网络连接超时或者连接异常关闭时,应用程序都应该在WARNING这一级别进行记录;每次关闭连接时,应该使用INFO级别进行记录,如果日志显得太长,也可以使用DEBUG级别。每次打开连接时,应该使用DEBUG级别记录,并且尽可能多地包含所连接终端的信息。
4)确保冒烟测试在部署时检查所有的连接,找出潜在的路由或连接问题
5)确保集成测试环境的网络拓扑结构尽可能与生产环境相似,包括使用同样的硬件和物理连接。以这种方式构建出来的环境甚至可以作为硬件故障时的一个备用环境。
10.6 虚拟化
虚拟化有助于减少部署软件所花费的时间,并用一种不同的方式来降低与部署相关的风险。当将其应用到部署流水线中时,它可以算是简化环境维护和准备工作最有用的技术了。虚拟化也有助于提高对功能需求和非功能需求的测试能力。
1)虚拟环境的管理
对需求的变化作出快速响应、固化、硬件标准化、基线维护更容易
2)虚拟环境和部署流水线
虚拟化技术提供了一个简单的机制来创建系统所需的环境基线;很容易将任何环境恢复到原有状态;通过使用虚拟服务器来做主机环境的基线使创建生产环境的副本变得更容易;实现一键部署应用程序任意版本。
3)用虚拟环境做高度的并行测试
虚拟化提供了一种绝好的方法来处理多平台测试。只要为应用程序可能运行的每种平台创建虚拟机,并在其上创建VM模板,然后在所有这些平台上并行运行部署流水线中的所有阶段就行了。可以使用同样的技术让测试并行化,从而缩短代价高昂的验收测试及容量测试的反馈周期。
10.7 基础设施和应用程序的监控
当创建监控策略时,需要考虑以下四个方面
对应用程序和基础设施进行监测,以便可以收集必要的数据
存储数据,以便可以很容易拿来分析
创建一个信息展示板,将数据聚合在一起,并以一种适合运维团队和业务团队使用的形式展现出来
建立通知机制,以便大家能找出他们关心的事件
1)收集数据
硬件、操作系统、中间件、应用程序
2)记录日志
3)建立信息展示板
4)行为驱动的监控
11 数据管理
对数据库进行版本管理,使用DbDeploy这样的工具管理数据迁移过程的自动化
努力保持数据库模式修改的向前和向后兼容性,以便把数据的部署和迁移问题与应用程序的部署问题分开
确保在准备过程中,测试可以创建它们所依赖的数据,并确保数据是分开的,以保证不会影响那些同时运行的其他测试
只保存不同测试之前应用程序启动所需要的测试数据,以及一些非常通用的引用数据
尽可能使用应用程序的公共API为测试创建正确的初始状态
在大多数情况下,不要在测试中使用生产数据集的副本。创建自定义数据集既可以通过细心选择生产数据集的最小子集来实现,也可以通过运行验收测试和容量测试来实现
11.1 数据库脚本化
数据库的初始化和所有的迁移都需要脚本化,并提交到版本控制库中。
11.2 增量式修改
1)对数据库进行版本控制
管理数据库变更的技术需要达到以下两个目标:首先,要能够持续部署应用程序而不用担心当前部署环境中所用的数据库版本。其次,部署脚本只要将数据库向前或向后更新到与应用程序相匹配的版本即可。
2)联合环境中的变更管理
对“哪个应用使用了哪个数据库对象”进行登记是个很有效的方法,这样你就知道哪次修改会影响哪些应用程序了。同时要确保在修改时已与维护其他应用程序的团队达成一致。
11.3 数据库回滚和无停机发布
1)保留数据的回滚
2)将应用程序部署与数据库迁移解耦
11.4 测试数据的管理
1)为单元测试进行数据库模拟
用测试替身对象来替代那些访问数据库的代码、使用假的数据库
2)管理测试与数据之间的耦合
依赖测试的独立性、适应性测试或测试的顺序性,测试的独立性在灵活性和扩展性方面较好
3)测试独立性
测试结束后将数据库中的数据状态恢复到该测试运行之前的状态;数据的功能性分隔
4)建立和销毁
5)连贯的测试场景
这种紧耦合的依赖性太大
11.5 数据管理和部署流水线
1)提交阶段的测试数据
要将设计分成多个互相独立的组件和测试,使用测试替身对象来模拟依赖
2)验收测试中的数据
尽可能减少测试对大型复杂数据结构的依赖
3)容量测试的数据
使用数据重用策略
4)其他测试阶段的数据
利用生产数据的一个子集或运行一些自动化验收测试或容量测试之后产生的数据库,为手工测试创建一个定制数据集
12 组件和依赖管理
组件是指应用程序中的一个规模相当大的代码结构,它具有一套定义良好的API,而且可以被另一种实现方式代替。对于一个与组件的软件系统来说,通常其代码库被分成多个相互分离的部分,每个部分通过个数有限的定义良好的接口提供一些服务行为,与其他组件进行有限的交互。基于组件的设计通常被认为是一种良好的架构,具有松耦合性,是一种鼓励重用的设计。
12.1 保持应用程序可发布
为了在变更的同时还能保持应用程序的可发布,有如下四种应对策略
1)将新功能隐藏起来,直到它完成为止
把新功能直接放进主干,但对用户不可见;通过配置项开关来管理
2)将所有的变更都变成一系列的增量式小修改,而且每次小的修改都是可发布的
3)使用通过抽象来模拟分支的方式对代码库进行大范围的变更
4)使用组件,根据不同部分修改的频率对应用程序进行解耦
12.2 依赖
在构建或运行软件时,软件的一部分要依赖于另一部分,就产生了依赖关系。在软件项目中,有两种适当的方法来管理库文件,一种是将它们提交到版本控制库中,另一种是显式地声明它们,并使用像Maven或Ivy这样的工具从因特网或者从你所在组织的公共库中下载。
12.3 组件
1)如何将代码库分成多个组件
它将问题分成更小且更达意的代码块
组件常常表示出系统不同部分代码的变化率不同,并且有不同的生命周期
它鼓励我们使用清晰的职责描述来设计并维护软件,反过来也限制了因修改产生的影响,并使理解和修改代码库变得更容易
它给我们提供了额外的自由度来优化构建和部署过程
2)将组件流水线化
每次提交修改时,就应该构建并测试整个应用。在大多数情况下,我们建议将整个软件系统作为一个整体来构建,除非是反馈过程太长。然而,在很多现实场景下,系统会受益于将其分成多个不同的构建流水线。
应用程序的某些组成部分有不同的生命周期
应用程序的几个功能领域由不同的团队负责,那么这些团队可能都会有自己的组件
某些组件使用不同的技术或构建流程
某些共享组件被不同的几个项目所用
组件相对稳定,不需要频繁修改
全面构建整个应用程序所花时间太长,但为每个组件创建一个构建会更快
3)集成流水线
当创建集成流水线时,需要牢记部署流水线的两个通用原则:快速反馈和为所有相关角色提供构建状态可视化。如果流水线或流水线链太长的话,会让反馈时间变长。一种解决方案是,在生成了二进制文件并通过单元测试之后就立即触发下游的流水线。
12.4 管理依赖关系图
1)构建依赖图
每个组件都应该有自己的构建流水线,当其源代码被修改时或者上游依赖有变化时都应该触发它的构建流水线。当该组件成功通过它自己的所有自动化测试时,就应该触发下游依赖。
2)为依赖图建立流水线
首先,为了增加反馈的速度,一旦任何一个项目部署流水线的提交阶段完成了,就要触发下游的项目,并不需要等待验收测试全部通过,只要下游项目所需的二进制文件已经产生就行了。除了向手工测试环境和生产环境部署意外,其他的触发都是自动的。团队要能够追踪在应用程序的某个具体版本中每个组件的源是什么。一个好的持续集成工具不仅可以做到这一点,还应该能够展示它是由哪些徐建的哪个版本集成在一起的。持续集成工具还要确保在每个流水线实例中,从前到后每个组件所有的版本都是一致的。它应该防止依赖地狱这样的事,并确保当版本控制库中的某次变更影响到多个组件时,它只能通过流水线传播一次。
3)什么时候要触发构建
一方面,最好保持与上游依赖最新的版本一致,以确保得到最新的功能和已修复的缺陷。而另一方面,集成每个依赖的最新版本会有一定的成本,因为要花时间来修复这些新版本带来的破坏。需要考虑的点是对这些依赖的新版本的信任度有多高。如果所依赖的组件是团队自己开发的,通常能快速且简单地修复由于API变更引起的问题,这样,频繁集成是最好的。如果组件足够小,最好就让整个应用只有一个构建。假如上游依赖组件是由你所在公司的其他团队开发的,那么最好这些组件有它们各自的流水线。然后可以再判断是使用上游依赖组件每次变更后的最新版本,还是仍旧使用某个具体版本。这个决定既依赖于它们变化的频率,又依赖于上游团队解决问题的速度。
4)谨慎乐观主义
为依赖图增加触发类型,“静止类型”的上游依赖发生变化后,不会触发新的构建;“活跃类型”的上游依赖发生变化后就一定会触发新的构建;如果某个“活跃类型”的上游依赖发生变更后触发了构建,并且这次构建失败了,那么就把上游依赖标记为“慎用类型”,并且将这个上游依赖的上一次成功的组件版本标记为“好版本”。
12.5 管理二进制包
1)制品库是如何运作的
不建议将二进制产物提交到版本控制库中。如果能重新生成它们,就不需要它们。为了达到这一点,版本控制系统就要包含重建这些二进制包所需的所有内容,包括自动化构建脚本。无论这些产物能保存多长时间,都应该一直保存每个产物的散列值,以便可以验证生成二进制包的源代码是否正确。
2)部署流水线如何与制品库结合
实现部署流水线需要做两件事:一是将构建过程中的产物放到制品库里,二是以后需要时能把它取出来。
编译阶段会创建需要放到制品库的二进制文件
单元测试和验收测试阶段会去除这些二进制文件,并在其上运行单元测试和验收测试,将生成的测试报告放到制品库中,以便开发人员查看结果
用户验收测试阶段是将二进制文件部署到UAT环境中,用于手工测试
发布阶段是取出二进制文件,将其发布给用户或部署到生产环境中。
随着候选发布版本在流水线中的进展,每个阶段的成功或失败都记录到索引文件中。流水线的后续阶段依赖于该文件中的状态记录,即只有已经通过验收测试的二进制文件才能用于手工测试和后续阶段。
13 版本控制进阶
13.1 版本控制系统
版本控制系统(也叫源文件控制或修订控制系统)用于维护应用程序每次修改的完整历史,包括源代码、文档、数据库定义、构建脚本和测试,等等。然而,它也有另一个重要的用途,让团队一起工作在应用程序的不同部分,同时维护系统记录,即应用程序的权威代码基。
13.2 分支与合并
分支的理由有三种。为了发布应用程序的一个新版本,需要创建一个分支;当需要调研一个新功能或做一次重构时;当需要对应用程序做比较大的修改。缺点是会阻碍持续集成,且需要花费额外的时间合并和解决冲突。
因此,需要对构建软件所需要的所有内容进行版本控制;需要有一个合理的流程来支持合并;只为发布创建长周期的分支,新开发的代码总是被提交到主干上。分布式版本控制系统在软件交付方式上有重大且积极的影响。
13.3 基于流的版本控制系统
基于流的版本控制系统可以把一系列修改一次性应用到多个分支上,从而减少合并时的麻烦。流之间是可以相互继承的,如果你把某次修改应用到一个指定的流上,它的所有子孙流都会继承那些修改。可以应用在下发修复补丁包和向代码基中添加第三方库的新版本时。
不能每天向共享主干提交代码是不符合持续集成实践要求的。有很多办法来解决这一问题,但这需要很强的纪律性,而且仍旧不能完全解决大中型团队所遇到的窘境。最佳规则是尽可能频繁地晋级修改,并在开发人员所共享的流上尽可能频繁且尽可能多地运行自动化测试。
13.4 主干开发
确保所有的代码被持续集成
确保开发人员及时获得他人的修改
避免项目后期的合并地狱和集成地狱
缺点是每次向主干签入并不都是可发布状态。且软件需要良好的组件化、增量式开发和特性隐藏。
13.5 按发布创建分支
一直在主干上开发新功能
当待发布版本的所有功能都完成了,且希望继续开发新功能时才创建一个分支
在分支上只允许提交那些修复严重缺陷的代码,并且这些修改必须立即合并回主干
当执行实际的发布时,这个分支可以选择性地打一个标签
13.6 按功能特性分支
每天都要把主干上的所有变更合并到每个分支上
每个特性分支都应该是短生命周期的,理想情况下应该只有几天,绝对不能超过一个迭代周期
活跃分支的数量在任意时刻都应该少于或等于正在开发当中的用户故事的数量。除非已经把开发的用户故事合并回主干,否则谁都不能创建新分支
在合并回主干之前,该用户故事应该已经由测试人员验收通过了。只有验收通过的用户故事才能合并回主干
重构必须即时合并,从而将合并冲突最小化。这个约束非常重要,但也可能非常痛苦,进而限制了这种模式的使用
技术负责人的一部分职责就是保证主干的可发布状态。他应该检查所有的合并,有权拒绝可能破坏主干代码的补丁
13.7 按团队分支
创建多个小团队,每个团队自己都有对应的分支
一旦某个特性或用户故事完成了,就让该分支稳定下来,并合并回主干
每天都将主干上的变更合并到每个分支上
对于每个分支,每次签入后都要运行单元和验收测试
每次一个分支合并回主干时,在主干上都要运行所有的测试(包括集成测试)
14 持续交付管理
风险管理是一个过程,它确保:
项目的主要风险已经被识别
已有适当的缓解策略对这些风险进行管控
在整个项目过程中,持续识别和管理风险
网友评论