一、软件交付的问题


1、一个简单的部署流水线

  • 提交阶段
    • 编译
    • 单元测试
    • 分析
    • 构建安装包
  • 自动化验收测试
  • 自动化容量测试
  • 手工演示、探索性测试
  • 发布

2、常见反模式

  • 手动部署软件
  • 开发完成后才向类生产环境部署
  • 生产环境的手工配置管理

3、如何实现目标

目标:快速交付

  • 自动化
  • 频繁做
  • 无论什么修改都应接触反馈流程
  • 反馈应该尽快发出
  • 交付团队必须接收反馈,并根据它做出相应的行动

4、收效

  • 授权团队:所有人员都能自服务,而不是昂贵的沟通
  • 减少错误:可以尽早发现问题
  • 缓解压力:发布变得风险低
  • 部署灵活

5、候选发布版本

每次提交代码都可以产生一个可发布版本。

6、软件交付的原则

  • 为软件的发布创建一个可重复且可靠的过程
  • 将几乎所有的事情自动化
  • 把所有的东西都纳入版本控制
    • 代码
    • 文档
    • 配置
    • 各种脚本
    • 依赖
  • 提前并频繁的做让你感到痛苦的事情
  • 内建质量
    • 测试不是一个阶段,而是贯穿软件的整个历程
    • 测试不是测试人员的领域,交付团队的每个人员都应该对应用程序的质量负责
  • DONE意味着已发布
    • 没有完成80%的说法(估计往往不准确)
  • 交付过程是每个成员的责任
  • 持续改进

二、配置管理


2、使用版本控制

对所有的内容使用版本控制

  • 不仅将源代码相关文件放入版本控制,还可以将系统镜像等二进制文件放入版本控制
  • 不推荐将源代码编译后的二进制文件放入代码库

频繁提交代码到主干

一个矛盾:

  • 满足每个提交都能通过构建,但是有些任务复杂,需要花费很长时间
  • 需要频繁的提交代码

解决方法:

  • 创建分支(不推荐)
  • 采用增量方式开发新功能

提交代码的好的做法:

  • 提交前运行测试套件
  • 增量式引入变化,每完成一个小功能或重构就提交一次代码

使用意义明显的提交注释

3、依赖管理

外部库文件管理

是否将外部依赖的库文件放入版本控制(各有利弊)

组件管理

4、软件配置管理

配置与灵活性

  • 高度可配置的系统代表者灵活性
  • 但是“终极配置”是一种反模式,因为,修改配置信息的风险小于修改代码的风险低可能是一个错觉
    • 配置信息无法像代码一样进行语法检查、编译检查
    • 配置信息的正确性难以验证
  • 高度可配置的系统问题
    • 经常性的导致分析瘫痪
    • 系统配置工作变得十分复杂,抵消了其在灵活性上带来的好处
  • 应该将精力花在提供有高价值的可配置程度较低的系统上

配置的分类

  • 生成二进制文件时,构建脚本可以在构建时引入相关的配置,并将其写入二进制文件
  • 打包时,将配置信息一同打包到程序包中
  • 在安装部署软件程序时,脚本会获取必要的配置信息
  • 软件在启动或运行时可获取配置

做法

  • 软件包中包含默认配置
  • 在测试或运行时,覆盖默认配置

应用程序的配置管理

  • 如何描述配置信息(语法)?
    • 键值对的方式
    • yaml
    • json
    • java 配置文件
    • xml
  • 部署脚本时如何获取这些配置信息(保存位置)?
    • 数据库
    • 版本控制库
    • 文件目录
    • 注册表
    • 系统环境变量
    • 配置服务器
  • 在环境、应用程序、以及应用程序各个版本中每个配置信息有什么不同?

如何为配置信息建模

  • 新增一个环境、可以为这个环境新增一套配置
  • 创建一个程序的新版本,增减后应爱可以回滚
  • 不同环境配置有效
  • 数据库服务器环境迁移,应确保简单修改配置即可实现
  • 通过虚拟化技术管理环境

系统配置需要进行测试

跨应用的配置管理

程序配置管理应在项目启动之初就应该考虑的内容

管理配置信息的原则

  • 程序生命周期:何时注入配置,打包?部署安装?启动?运行?视情况而定
  • 将配置项与源代码保存在同一代码库。但是值要保存在别处。密码不能放入版本控制库
  • 应该通过自动化的方式在代码迁出时,将配置项配置好
  • 配置系统应该可以根据应用、版本、环境,为打包、安装、部署脚本提供不同的配置值。每个人都能看到配置值
  • 配置项使用明确的命名习惯,能自注释
  • 确保配置信息的模块化和封闭性
  • 不要重复
  • 配置最小化
  • 避免过度设计,尽可能简单
  • 确保测试覆盖了配置操作

5、环境管理

避免“临时决定”,手工配置环境,要自动化的生成环境

环境配置内容如下:

  • 操作系统的版本、补丁及配置设置
  • 应用程序依赖的软件包
  • 网络
  • 外部服务及版本配置
  • 现有的数据和其他先关信息如数据库初始数据

原则

  • 将二进制文件和配置信息分离
  • 将所有配置信息保存到一处

第三方软件的要求

  • 可以通过命令行执行安装且不需要用户干预。
  • 程序配置可以通过版本控制系统管理

环境管理工具

  • Puppet、CfEngine
  • 虚拟化
  • 容器

环境变更过程管理

系统环境的变更要严肃对待,不能轻易修改,要经过严格审核。对待测试环境应当像面对生产环境一样

6、小结

好的配置管理应满足如下内容

  • 仅依靠版本库中的内容就可以构建生产系统
  • 可以回滚到以前某个正确状态
  • 可以在测试、试运行、上线环境下以同样的方式创建部署环境

将如下内容加入变更控制

  • 程序的源代码、构建脚本、测试、文档、需求、数据库脚本、代码库、配置文件
  • 用于开发、测试和运维的工具集
  • 用于开发、测试和生产运行的所有环境
  • 应用程序的整个软件栈,包括二进制代码及相关配置
  • 在应用程序的整个生命周期(构建、部署、测试及运维)的任意一种环境上,与该应用程序相关联的配置

三、持续集成


2、实现持续集成

准备工作

  • 版本控制
  • 自动化构建工具(命令行可运行)
  • 团队共识和纪律性

持续集成工具

  • CruiseControl
  • Hudson
  • ThoughtWorks Studios 的GO
  • JetBrains的TeamCity
  • Bamboo
  • Pulse

使用步骤

  • 检查持续构建中是否有任务,有的话等待运行完成,
    • 若失败了,协助修复,然后提交自己的代码
    • 一旦构建完成且测试通过,就更新自己的代码
  • 在自己的机器上执行构建脚本,运行测试确保正确
  • 本地构建测试
    • 成功,将其提交到版本控制上
      • 等待结果
    • 失败,解决问题,转到第三步

3、持续集成的前提条件

  • 频繁提交
  • 创建全面的自动化的测试套件
  • 保持较短的构建和测试过程
  • 管理开发工作区,确保开发机构建测试

4、使用持续集成软件

基本构成

  • Web服务器:展示构建结构和配置
  • 后台进程:进行构建及测试
  • 通知方式
    • 独立硬件设备
    • 软件插件
    • 邮件等

5、必不可少的实践

  • 构建失败后不要提交新的代码(保持构建通过状态)
  • 提交前在本地运行所有的提交测试,或构建服务提供这个服务(预测试提交、个人构建、试飞测试)
  • 等到构建测试通过后,再进行工作
  • 回家之前,构建状态必须处于成功状态
  • 时刻准备回滚到前一个版本
  • 回滚之前确定一个修复时间
  • 不要将失败的测试注释掉
  • 为自己导致的问题负责
  • 测试驱动开发

6、推荐的实践

  • 极限编程开发实践(包括代码集体所有权)
  • 若违反构架原则,就让构建失败(分清系统边界)
  • 若测试运行变慢,就让构建失败(不是性能测试)
  • 若有编译警告或代码风格问题,就让测试失败

7、分布式团队

  • 软件开发流程不同
  • 集中式持续集成
  • 技术问题(网络问题)

8、分布式版本控制系统

在此系统使用持续集成,需要设定主干代码库

9、小结

  • 一个巨大的可视化指示器,用于显示系统所收集的信息,以提供高质量的反馈
  • 结果报告系统,以及针对自己测试团队的安装包
  • 为项目经理提供的关于应用程序质量的数据提供程序
  • 使用部署流水线,可以将其延伸到生产环境,为测试人员和运维团队提供一键式部署环境

四、测试策略的实现


2、测试方法的分类

常规的分类方法有两个维度

  • 维度1
    • 业务导向
    • 技术导向
  • 维度2
    • 支持开发过程的
    • 评判项目的

交叉起来有四种类型

  • 业务导向且支持开发过程的测试——自动化功能验收测试
  • 技术导向且支持开发过程的测试——自动化单元测试、集成测试、系统测试、系统测试
  • 业务导向且评判项目的测试——手工的演示、易用性测试、探索性测试
  • 技术导向的且评判项目的测试——手工或者自动的非功能验收测试(容量测试、安全性测试)

(1)业务导向且支持开发过程的测试

通常称为:自动化功能验收测试

在开发之前,需求初步确定阶段,可以用该测试产生需求说明文档(BDD行为驱动开发)

测试代码覆盖率达到80%可以称为全面测试

自动化的功能验收测试的好处

  • 加快反馈
  • 减少测试人员工作负载
  • 也是回归测试的一部分
  • 可以将需求分析写成测试代码

测试的维护成本可能很大

测试脚本与实现分离的工具:也就是,使用自然语言按照一定的格式进行描述功能,开发人员根据表述,提取信息绑定到测试的实现上

  • Cucumber
  • JBehave
  • Concordion
  • Twist

并非所有的内容都能自动化,但是能自动化的内容要尽量自动化

(2)技术导向且支持开发过程的测试

  • 单元测试
    • 依赖测试替身、模拟系统其他部分
    • 不应访问数据库、文件系统、不应与外部系统交互
    • 覆盖率至少80%
    • 要求运行速度块
  • 组件测试
    • 用于测试更大的功能集合,设计IO
    • 需要访问数据库、文件系统、与外部系统交互
    • 可能会慢些
  • 部署测试
    • 测试程序是否被正常部署

(3)业务导向且评判项目的测试

通过手工检验测试如下内容

  • 交付的内容是否符合用户需求
  • 验证需求规格是否完美合理

方法

  • 每个迭代周期结束后进行演示
  • 探索性测试
  • 易用性测试
  • Beta测试
(4)技术导向的且评判项目的测试

主要是非功能测试

  • 容量
  • 安全
  • 可用性
  • 安全性

(5)测试替身

  • 哑对象(dummy object)指被传递但是不会被使用的对象
  • 假对象(fake object)真正可以被使用的,单不适用于生产环境如内存数据库
  • 桩(stub)测试中为每个调用提供一个封装好的响应
  • spy 是一种记录一些关于他们如何被调用的桩
  • 模拟对象(mock object)编程时就设定了它预期要接收的响应。如果收到不期望的结果,将抛出异常

3、现实中情况的应对措施

(1)新项目

准备

  • 选择技术平台和测试工具
  • 建立一个简单的自动化构建
  • 制定遵守invest原则的用户故事及考虑验收条件
    • 独立的(Independent)
    • 可协商的(Negotiable)
    • 有价值的(Valuable)
    • 可估计的(Estimable)
    • 小的(small)
    • 可测试的(Testable)

严格遵守以下流程

  • 客户分析师、测试人员定义验收条件
  • 测试人员和开发人员一起基于验收条件实现验收测试自动化
  • 开发人员编码满足验收条件
  • 只要有自动化测试失败,将修复工作置为高优先级

(2)项目进行时

  • 选择价值高、重要的功能点进行测试自动化

(3)遗留系统

  • 如果没有,就从最重要的点开始制定一个,并逐步延伸

(4)集成测试

测试系统中对外部服务有依赖的模块,如依赖第三方API等

4、流程

  • 团队有好的沟通
  • 每次迭代开发之前会议

管理待修复的缺陷列表

  • 零缺陷,立即解决
  • 缺陷列表可视化
  • 确定优先级

5、小结

  • 测试仅是测试的职责,更是每个人的责任
  • 建立完善的反馈环

五、部署流水线


2、什么时部署流水线

指软件从版本控制库到用户手中过程的自动化表现形式。有如下阶段

提交阶段(编译、单元测试、分析、构建安装包)
	|
	v
验收测试阶段
	|
	v
用户验收测试、容量测试
	|
	v
生产环境

关键是足够的测试集和自动化。

部署流水线包含如下阶段:

  • 提交阶段
  • 自动化验收阶段
  • 手工测试阶段
  • 发布阶段

特点

  • 自动化
  • 快速可视化反馈
  • 频繁执行

3、部署流水线的相关实践

  • 只生成一次二进制包
  • 对不同的环境采用同一部署方式
    • 将平台相关性的内容通过配置服务器和环境变量切换
  • 对部署进行冒烟测试
  • 向生产环境的副本中部署
  • 每次变更都要立即在流水线中传递
  • 只要有一个环节失败,就停止整个流水线

4、提交阶段

包含如下内容

  • 编译代码
  • 运行一套提交测试
  • 为后续阶段创建二进制包
  • 执行代码分析检查代码健康状态
  • 为后续阶段准备做准备工作

5、自动化验收测试之门

自动化验收测试阶段的任务主要是:自动化功能验收测试

6、后续阶段

  • 手工测试
    • 探索性测试
    • 易用性测试
  • 非功能测试
    • 容量
    • 安全

7、发布准备

  • 让参与项目交付过程中的人共同创建并维护一个发布计划
  • 尽可能的自动化测试
  • 在类生产环境演练
  • 意外情况有撤销发布能力
  • 作为升级和撤销一部分,指定配套迁移和数据迁移策略

8、实现一个部署流水线

  • 对价值流进行建模,创建一个可工作的简单框架
  • 将构建和部署流程自动化
  • 将单元测试和代码分析自动化
  • 将验收测试自动化
  • 将发布自动化
  • 持续演进

9、量度

  • 自动化覆盖率
  • 代码库特征
  • 缺陷数量
  • 交付速度
  • 每天提交到代码库的次数
  • 每天构建次数
  • 每天构建失败次数
  • 每次构建所花费的时间,包括自动化测试时间

六、构建与部署的脚本化


2、构建工具概览

  • Make
  • Ant
  • NAnt、MSBuild
  • Maven
  • Rake
  • Buildr
  • Psake

3、构建部署校本化的原则和实践

  • 为部署流水线的每个阶段创建脚本
  • 使用恰当的技术部署应用程序
  • 使用同样的脚本向所用环境部署
  • 使用操作系统自带的包管理工具
  • 确保部署流程是幂等的
  • 部署系统的增量式演进

4、面向JVM的应用程序的项目结构

  • 项目结构:Maven项目目录结构

5、部署脚本化

  • 让脚本登录到机器上,运行适当的命令集
  • 写个本地脚本,在远程机器上安装一个代理,由代理从版本库中获取到脚本,并执行

部署是一种层级结构

  • 应用、服务、组件 和 应用配置
  • 中间件和中间件配置
  • 操作系统、操作系统配置
  • 硬件

测试环境的配置(比如测试数据库环境、后端服务等)

  • 使用简单的冒烟测试

6、小提示

  • 总是使用相对路径
  • 消除手工步骤
  • 从二进制包到版本控制库的内建可追溯性
  • “test”不应该让构建失败
  • 用集成冒烟测试来限制应用程序

七、提交阶段


2、提交阶段的原则和实践

  • 提供快速有用的反馈
    • 尽早发现错误
    • 尽可能多的运行流水线,即使单元测试失败,也要运行接下来的测试,以获取更多的错误报告以便于一次性解决
  • 何时令提交阶段失败
    • 根据情况选择一个阈值(编译警告数量、测试样例失败数目等)
    • 提交阶段失败,就立即停下手头工作,进行修复
  • 精心对待提交阶段
    • 对待构建脚本、运行脚本要像对待代码一样
    • 脚本要不断的演进优化
  • 让开发人员也拥有所有权
    • 开发人员可以看懂并理解和控制构建流水线
    • 开发人员和维护人员应该对构建系统维护并负责
  • 在超大项目中指定一个构建负责人
  • 提交阶段的结果

3、提交阶段的结果

提交阶段的输入是源代码,输出如下

  • 二进制包
  • 报告
    • 测试结果
    • 代码库分析报告
      • 测试覆盖率
      • 圈复杂度
      • 复制粘贴分析
      • 输入输出耦合度

制品库

  • 用于存放最终可运行的部分二进制包和结果报告的仓库
  • 应该与版本库关联,当二进制包被删除应可以根据源代码库重新生成
  • 制品库应该提供RESTful接口
  • 可以使用类似Maven的工具
  • 提交阶段根据源代码生成二进制包,并放入制品库
  • 在后续的步骤(验收测试、手工测试、性能测试、发布阶段)的输入都是制品库中的二进制包
  • 在发布之后将该二进制包标记为已发布

4、提交测试套件的原则与实践

  • 大部分是单元测试
    • 运行速度快
    • 代码覆盖率达到80%
    • 使用mock和stub,提高速度
  • 避免用户界面
    • 不要将提交阶段的单元测试设置在用户界面
    • 将用户界面测试放入验收测试阶段
  • 使用依赖注入
    • A依赖于B,使用以来注入后,在更改B的实现,A的代码不需要改变即可实现变更
    • 因此,可方便的在测试A时,创建一个mock B
  • 避免使用数据库
  • 单元测试避免使用异步
  • 使用测试替身(mock)(mock和stub区别是:mock是自动生成一个代理、stub可能需要手写)
    • Mockito
    • Rhino
    • EasyMock
    • JMock
    • NMock
    • Mocka
  • 最少化测试中的状态
  • 时间伪装
    • 系统中所有使用系统时间的地方使用一个获取时间的封装,这样就可以使用mock技术进行测试
  • 提交测试花费的事件最好在5分钟内,不能超过10分钟

八、自动化验收测试


  • 功能性验收测试
  • 非功能性验收测试
    • 容量
    • 性能
    • 可修改性
    • 可用性
    • 安全性
    • 易用性

与单元测试的区别是:验收测试是针对业务的

2、为什么验收测试是十分重要的

  • 手工测试对复杂项目成本很高,且限制了频繁发布
  • 只有验收测试才能证明软件对用户的价值,测试的是用户场景
  • 能检测出线程问题(死锁)、架构问题、环境问题
  • 可以保证程序发生大规模修改后是否仍然正确
  • 没有验收测试,测试人员的负担会十分重

(1)如何创建可维护的验收测试套件

  • 注重分析过程,验收测试来源于验收条件
  • 遵循INVEST原则
  • 验收测试应该是分层的
    • 第一层是:验收条件
    • 第二层是:测试实现层
      • 使用领域语言实现测试,
      • 不能是与内部API和UI耦合
    • 第三层是:应用程序驱动层
      • 理解如何与应用程序进行交互,来执行一系列动作,

(2)GUI上测试

  • 如果GUI仅是展示数据,不包括逻辑(好的设计),可以绕过界面直接在业务逻辑层进行测试

3、创建验收测试

(1)分析人员和测试人员的角色

分析人员:

  • 代表客户和用户
  • 和客户工作:识别需求,排定优先级
  • 和开发人员工作:确保开发人员理解需求
  • 和测试人员工作:确保验收条件被合理阐释

测试人员: 。。。

(2)迭代开发项目中的分析工作

(3)将验收条件变成可执行的规格说明书

行为驱动开发。验收测试的特定语言:

  • 假如(Given)初始条件
  • 当(When)某个事件发生时
  • 那么(Then)就会有什么结果

4、应用程序驱动层

是知道如何与应用程序打交道的层次。应用程序驱动层所用的API是以某种领域语言的表达。

(1)如何表达验收条件

  • 外部DSL和程序API各有优缺点

(2)窗口驱动模式

让测试与GUI解耦

5、实现验收测试

(1)测试中的状态

  • 要是状态依赖最小化
  • 维护最小依赖数据集
  • 利用功能间的隔离性进行测试

(2)过程边界、封装和测试

(3)管理异步和超时问题

(4)使用测试替身对象

5、验收测试阶段

验收测试在高层,错误可能并不是被测方的问题。所以可以进行测试录像。以方便查找失败原因

  • 确保一致处于通过状态
  • 部署测试:尽量与生产环境一致

6、验收测试性能

提高性能方法

  • 重构通用任务
  • 共享昂贵资源
  • 并行测试
  • 使用计算网格