测试的过程没太有太严格的分类标准,从使用的角度,我们可以从以下两个角度对测试进行分类。
软件测试大概分为单元测试、集成测试、系统测试、验收测试。以下为软件测试V型图:
测试可分为功能测试、稳定性测试、性能测试等。
发现软件的bug只是手段,软件测试的目的在于保证并提高产品质量。
如果产品继承自开源社区,开源社区一般有严格的测试标准(如openstack),但是我们仍然需要验证:
持续集成(Continuous integration)测试,是指在持续基础上不断执行测试以验证完整应用环境正确的行为。“持续”,即要求每次改变提交后,测试工作都要进行。因为需要不断进行回归测试,而且一般都对测试时间有一定要求,因此会大量使用自动化测试。
如果项目开发的规模比较小,对外部系统的依赖很小,那么软件集成不是问题。但是随着软件项目复杂度的增加,就会对集成和确保软件组件能够在一起工作提出了更多的要求。如果开发人员在后期才进行集成,到后期才发现问题,解决问题代价会很大,很有可能导致项目延期或者项目失败。
因此要求“尽早集成、经常集成”,在早期发现项目风险和质量问题,更容易解决问题,并保证开发进度。持续集成就出现在这样的背景下。
持续集成测试是一种软件开发实践:团队开发成员经常集成他们的工作,每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件从而减少项目整体风险。
通常当我们提及CI的时候,我们指的是针对完整、现实使用的环境进行的测试。这种测试可以称为集成测试,可以确保提交的针对某个组件的修改不会导致其他组件的失效。集成测试对于如Openstack的多组件多项目的复杂系统尤为重要,而且适用于子系统间没有过多的耦合依赖的情况。
Openstack主要使用Gerrit进行代码评审及管理。社区的Gerrit运行于。Gerrit工作流如下:
当贡献者向Openstack提交一个patch时,他会将代码提交至由Girrit管理的git服务器上。Gerrit控制着哪个用户或组织可以提交代码、合并代码、管理代码库。当贡献者提交代码至review.openstack.org时,Gerrit会创建一个变更集(Changeset)来代表所提交的代码。原提交者和其它贡献者可以对变更集提交额外的修改。Gerrit会收集所有这些更改而汇集成变更集记录。以下为一个待评审的变更集,其中可以看到有很多Patch,而每个Patch都是对原始提交的一次修改。
Gerrit中的每个Patch都有三个标签属性。每个人都可以评论变更集或评审代码。在Patch标签属性的“Code-Review”一列中显示了对代码的所有评审:
非Openstack核心团队的人员可以在评审代码后作出+1(好)、0(无评价)、-1(不建议合并)的评价。核心团队的成员可以给出非核心成员可给出的所有评价,此外还可以给出+2(好,approved)、-2评价(不要提交)。
标签属性中另外一列为“Verified”。只有Gerrit的非交互用户(non-interactive users)如Jenkins,才能在此属性上添加Verified标签。Verified一列可设置的值为+1(check pipeline测试通过)、-1(check pipeline测试未通过)、+2(gate pipeline测试通过)、-2(gate pipeline测试未通过)。此外,第三方搭建的外部测试平台也属于非交互用户,但是只能设置+1、-1。
标签属性中最后一列为Workflow(原为Approve)。只有核心团队的成员可对此列进行标注。此列值只能为+1(Approved)、未通过,展现形式为有无对号标记:
这里所说的Gate是一个流程、一个动作,通过这个流程、动作,相关条件达不到的代码将被隔离在源代码分支之外,从而完成对目标代码的“守卫”。
Openstack采用一种称之为“Non-Human Gatekeeper”的模型来控制代码合并进特定代码分支。Gerrit就是其中的“the non-human”,它允许“Non-Interactive Users”合并代码到它管理的稳定的主干代码分支中。上游的Jenkins服务器以及运行于第三方环境中的Jenkins系统,就属于这些“Non-Interactive Users”。
那么这些“Non-Interactive Users”如何去判断是否将一个提交的Patch合并进目标代码分支呢?这是通过运行一些相关的测试来实现的。测试内容因项目不同而不同,但大体包括这些内容:单元测试、功能测试、集成测试、升级测试和代码风格测试。“Non-Interactive Users”通过这些测试来守卫(gate)一个特定项目的源代码树。
大多数Openstack项目都有单元测试和代码风格测试。单元测试在不同的Python版本上执行。风格测试用于验证代码风格是否符合Openstack Hacking和PEP8规定。单元测试和风格测试通过tox调用virtualenvs实现。
此外,还有针对完整安装的Openstack进行的集成测试。这部分测试时Tempest套件的一部分。最后,很多项目还有升级测试和数据库迁移测试包含在gate test中。
在未出现标准的自动化CI测试套件之前,社区的代码质量基本都由代码评审人员在本地手工测试保证。执行效率低,需要不断重复,而且因为对完整的环境测试很困难导致很难保证某个模块的修改对其它模块是否会产生影响。此外也无法验证在不同环境下是否运行正常。
而这一切都在社区的自动化CI测试套件出现后得到改善。这是一个完整的、标准化的、鲁棒性高的、自动化的持续集成测试平台,由社区的openstack-infra开发维护。它的核心是Gerrit代码控制评审平台、Zuul驱动的Gating System和Jenkins CI服务器。这个测试系统已被广泛使用,截止2013年9月,社区持续集成测试平台每小时执行720个测试任务,测试节点已增加至328个,不断并行进行测试。截止I版本,社区大概每天有400个测试通过的提交。
根据测试目的、测试精度、测试可靠度等的不同,社区的测试运行方式分为以下五种。
社区并无自己的设备,所有环境都是Rackspace、HP等云厂商捐赠的虚拟机实例。
2014年6月:目前大概有340个实例并行执行。实例大多本身运行于Openstack之上,具有8G RAM及相关配置,使用Ubuntu Precise系统。
各组件的工作流程图如下:
Gerrit主要负责代码的管理,具体可查看之前的章节。在Gerrit将通知事件添加到Gerrit事件流之后,Zuul组件开始发挥作用。
Zuul通过Gerrit事件,找到对应的pipeline进行后续处理。而pipeline中定义了需要执行哪些Jenkins任务。这些都是在Zuul的配置文件layout.yaml中定义的。例如如下为一个gate这个pipeline的片段:
- name: gate
description: Changes that have been approved by core developers...
failure-message: Build failed. For information on how to proceed...
manager: DependentPipelineManager
precedence: low
trigger:
gerrit:
- event: comment-added
approval:
- approved:
1
- event: comment-added
comment_filter: (?i)^\s*reverify( (?:bug|lp)[\s
#:]*(\d+))\s*$
start:
gerrit:
verified:
0
success:
gerrit:
verified:
2
submit:
true
failure:
gerrit:
verified:
-2
|
从中我们可以看到gate这个pipeline由gerrit的commit-added + approved或 commit-added +无bug标识的reverify事件触发。成功后会返回verified:2消息给gerrit,失败返回verified:-2。
类似,check pipeline会被gerrit的patchset-created或recheck事件触发,成功后返回verified:1,失败后返回verified:-1,与Test Run Style描述一致。Openstack的CI环境中主要用到了check、gate、post、pre-release、release、silent、experimental、periodic这几个pipeline,并分别给出了每个项目中这些pipeline对于哪些Jenkins任务。
而对于每个项目的每个pipeline,需要执行哪些任务,在此配置文件中project一节定义:
- name: openstack/cinder
template:
- name: python-jobs
...snip...
gate:
- gate-cinder-requirements
- gate-tempest-dsvm-full
- gate-tempest-dsvm-postgres-full
- gate-tempest-dsvm-neutron
- gate-tempest-dsvm-large-ops
- gate-tempest-dsvm-neutron-large-ops
- gate-grenade-dsvm
|
上述配置为对于cinder项目的gate这个pipeline,需要执行gate-cinder-requirement到gate-grenade-dsvm这些任务。
引入Zuul而不是直接使用Gerrit触发Jenkins的原因是,Jenkins每次只能执行一个任务。这种线性执行是因为考虑到复杂的依赖关系,但是这在需要测试的修改数量增加时就会验证影响效率:如果一个测试需要执行1小时,每天只能执行24次测试,只能对24个修改做出验证。通过引入Zuul使Jenkins并发测试成为可能。
Zuul同时还可以很好的处理具有复杂依赖关系的多个patch。它能监控正在执行的任务,并可提前结束掉因依赖的patch测试失败而必定失败的测试任务。
在原来的实现中,Zuul完成“事件-pipeline-任务”的匹配后,就可以调用Jenkins执行具体的任务开始实际的测试了。Jenkins用的是master/slave架构,一台master管理所有slave节点。但是Jenkins的设计初衷并不用于并行执行,它设计中某些点使用到了全局锁,因此在Jenkins的slave节点增加到一定数量后(大约100台),Jenkins的master节点就会出现问题而成为瓶颈。同时master节点是单点部署,无法完成HA等处理。为了扩展Jenkins而引入了Gearman。
加入Gearman后,Zuul不再与Jenkins直接交互,而是提交执行任务的请求给Gearman服务器,由Gearman服务器完成任务的分发。测试节点通过注册到Gearman服务器,使得Gearman获知其可用,并被分配任务。如Jenkins Master节点通过Gearman的插件连接到Gearman服务器并获得任务。
通过Gearman,CI测试架构具有了弹性:触发事件的可以不只是Zuul,而执行任务的可以不只是Jenkins。而通过多个Jenkins Master的部署,获得了HA功能。
经过上述流程,终于到了Jenkins实际执行的流程。CI环境中使用的是Master/Salve架构,同一个Master同时控制多个Slave节点进行工作。
每个Jenkins任务都需要配置Jenkins的config.xml文件实现。而在任务数量达到一定级别后,手工去配置每个任务会变得非常复杂。因此引入了Jenkins Job Builder (JJB)这个Python工具。JJB通过解析用户配置的YAML文件来自动生成config.xml。而YAML文件使用比较易读的语法,支持弹性的模板系统,且支持版本控制便于不断修改。这些特性使得配置大量Jenkins任务变得容易。
Zuul并不去指定具体Jenkins任务是什么,而是只指定对一个项目的一个pipeline需要去执行哪些Jenkins任务。具体指定每个Jenkins任务的内容是由JJB来做的。
在Jenkins的任务中,完成对代码的check out、build,然后进行按照定义进行实际的测试。
对于单元测试等可能不需要实际的Openstack环境去执行,而对于集成测试等则需要一整套的Openstack组件。对于这些测试, Jenkins任务会去实际搭建完整的Openstack环境。这个工作通过Devstack-Gate调用Devstack脚本实现。但是因为部署也属于Openstack的一部分特性,因此H版本之后社区正在考虑将部署部分也纳入测试项,替代Devstack。
测试中支持配置不同的组件,如替换数据库PostgreSQL,替换消息队列为ZMQ。
而在搭建环境之前还需要去申请搭建环境需要的资源(目前社区使用的是虚拟机实例)。Nodepool实现了资源的有效调度,并可方便的添加资源进入资源池。
在资源使用后,需要清空配置还原环境。这个是通过脚本恢复备份来实现的。
实际执行的测试用例也是在Jenkins的任务中定义,大多通过调用Tempest集成测试套件中的用例实现。
日志及检索,通过ELK技术栈,实现日志的实时监控:
严格的pep8要求,及Openstack自身对代码风格的要求。
通过Python脚本实现。主要通过python的unittest、mock和nose等单元测试库实现。
Tempest是一整套Openstack集成测试框架,它的实现基于python的unittest2测试框架和nose测试框架。Tempest对Openstack终端发起一系列API请求,并且对终端的响应进行验证。Tempest通过config文件来描述整个测试环境,包括compute API端点,Keystone server以及Glance server安装的镜像的UUID等信息。
测试的主要模块有如下几部分:
可以通过改写Tempest实现,增加功能测试的时间、场景的复杂度,增加一定的负载,即可实现满足要求的稳定性测试。
目前有Rally、scalability testing项目去实现。因为对运行环境有要求、负载压力较大,而且可能包含调优工作(此时可能会改变部署环境等),所以虽然已集成到CI环境中,但后续可能需要单独进行测试。
很多项目中还在gate的测试中包含升级和数据库迁移测试(如turbo-hipster)。在原有数据量比较大的情况下,就需要考虑升级测试。
还有很多测试未包含,但是社区在不断完善加入新的测试。
作为Openstack开发厂商,如何集成社区的CI框架到我们的开发过程中呢?