工业时代流水线的发明将生产任务的效率大大提升。同样,在软件开发过程中流水线的建立也能帮助我们更好的产出、提升效率。
流水线的建立准则应该符合每个团队自己的需求,比如你的团队管理策略、分支管理策略,接下来以我们最近的给客户做的案例进行总结说明:
我们首先需要制定我们流水线的策略,需要哪几个任务,进行任务的拆分:
iOS:
| 主分支每次提交进行构建触发单元测试
|
|--功能测试构建分发
| |
| |--各个执行功能测试的节点并行执行功能测试
| |
| |--聚合各个节点的功能测试结果输入报告
|
|--adhoc分发通知测试人员
|
|--enterprise分发通知测试人员
|
|--appstore提审核
|
Android:
| 主分支每次提交进行构建触发单元测试
|
|--功能测试构建分发
| |
| |--各个执行功能测试的节点并行执行功能测试
| |
| |--聚合各个节点的功能测试结果输入报告
|
|--打release包通知测试人员测试
|
|--打各种渠道包
其中的功能测试板块由客户的测试团队负责日常的监控以及维护,不干扰开发团队日常的开发。不是整个任务成功的必要条件,而在我之前的开发项目中,功能测试是由开发人员与测试人员一起结对书写,因此会把它作为发测试包的前置步骤,这点根据每个团队的实际情况考量。
工具集:
工欲善其事,必先利其器。要想自动化整个过程,离不开工具的支持。
通用:
Jenkins:用的最广持续集成工具,但是本身并不提供流水线功能,需要插件支持
Gem:ruby包管理工具,比如我们执行功能测试Appium、Calabash等都是通过gem来安装的。
rbenv:管理ruby,用它来统一ruby环境。
bundle:用来管理gem包,比如gem包的版本等。
rake:用ruby时间的类似于make的构建工具,我们的任务脚本使用rake来写的,选自己顺手的就好了。
cucumber:基于BDD的自动化测试框架
iOS:
shenzhen:对,深圳,作者写了一堆用城市命名的工具。这个是用来构建ipa以及分发的,不过我只用了它的分发功能,还是直接用的xcodebuild构建。
calabash:iOS端用来进行自动化功能测试的工具,基于cucumber。
Android:
Appium:类似于calabash的自动化测试框架。之所以没有在安卓上用calabash,是因为项目中用了蚂蚁金融的一个SDK,其对测试不太友好,不支持像calabash这样使用Instrumentation的框架。
关键设计:
持续对主分支进行构建:
我们需要保证开发团队的每一次代码提交都是能工作,能通过测试的,相比传统开发过程中在最后关头进行测试,大大降低了风险。
如下图,需要在任务配置中写上执行策略,比如你想每两分钟去检测一次你的代码库有没有代码变化,如果有变化,Jenkins会立刻开始执行构建。
拉取下了代码之后之后需要构建并执行单元测试,iOS使用xcodebuild
,安卓使用gradle
。
如何串连流水线:
当构建没问题之后,我们需要在构建后步骤中将下游的任务串连起来,这里有两种方式,一种是自动触发下游任务,一种是手动触发。比如我们的发包步骤就是运营人员手工操作,执行功能测试到合并报告就是自动进行的。
自动触发:
这里需要选择Trigger parameterized build on other projects,指明下游任务的名字。根据需要制定触发条件,以及传递的参数等。
手动触发:
这里需要选择Build other projects(manual steps),我们需要指定下游任务的名称。传递git commit过去是为了保证下游任务产品代码与当前保值一致,除此之外还会传递一些预定义的参数到下游。
并行执行功能测试
需求是需要在多台节点上并行地执行功能测试,比如我要在天津的一台机器用三星 note跑测试,我要在成都的一台机器用 另外一台手机跑。
为了解决这个需求,我需要加入一个多配置的任务,然后在Configuration Matrix中进行配置,如图,把能够执行这个任务的多个节点给选上。
如何合并多个cucumber报告
这里分两步,首先需要把每个节点的测试结果收集起来,然后传递到下游去,通过传递归档文件就可以完成这一步。比如我任务完成了之后会生成一个build
目录,我需要把多台节点的这个目录传递过去。这里需要注意的点在于,每个节点生成的结果会加上自己的机器前缀。比如会像这样mac_chengdu/build,我们需要使用通配符 ****/build/**表示。
接下来是合并,因为cucumber生成的报告结果可以是json的,这一步就是在解析json的结果,我fork了cucumber-html-reporter对进行修改,做成了一个node的命令行工具,可以参考这里。
关于自动化测试
iOS和安卓的开发中本身就提供单元测试的支持,比如iOS提供XCTest,安卓有JUnit,根据需要进行调整,比如iOS上我使用的Kiwi。
单元测试比较简单,主要看团队对这东西的认识。主要聊一下功能测试这块遇到的坑。
先说iOS,iOS上采用的calabash,一个是项目组之前也在用,二个是我在调研了appium之后,发现appium最新版本才开始支持XCUITest做功能测试,存在一些bug且功能不够完善,因此果断上calabash。
在安卓遇到的坑相对来说多一点,首先calabash在安卓上底层是用的Instrumentation
,调研过calabash的源码发现如果要让它支持UIAutomator
的话,基本上等于重新造一个轮子了,因此如果你的产品不支持Instrumentation
的话需要注意了。
那么appium呢,appium 配合cucumber在安卓上看起来不错的。我开开心心地用了起来,结果看报告发现这家伙在失败的时候不会主动截图,看样子得自己去实现了,好在cucumber提供了一些hook的方法,比如可以在每个执行步骤之后做点什么。
我一开始是在cucumber的AfterStep
中加入了截图的方法,可是发现并没有什么用,后来查了一会儿发现已经有人给cucumber提过这个[issue](bundle exec cucumber #{feature}--tags ~@pending --format json --out #{BUILD_DIR}/functionals.json --format NewHtml --out #{BUILD_DIR}/functionals.html),原来cucumber设计上就是这样考虑的,失败了的步骤不允许hook。
既然cucumber这一层做不了,那只能在appium这一层做手脚了。经过观察,失败场景大多是找不到元素,因此我需要解决的主要问题是在找不到元素的时候进行截图。我在appium-lib中找到了driver.rb
,看了下它提供了几个查找元素的方法,底层是用的selenium-webdriver
进行操作,那么我的需求应该在driver这层就能够解决,我只需要在这几个方法执行失败后加上截图保存的方法就好了。Ruby我不熟悉,我查了下有几种方式可以解决,你可以新建一个子类重写这几个方法,你可以利用ruby的动态性把这几个方法给动态的替换了。我这里采用的是第二种方式,代码如下:
module DriverExtension
def store_callback_after_fail(&block)
@execute = block
end
def take_screenshot_when_fail
@@screenshot_count ||= 0
$filename = "./build/" + "Screenshot-" + "#{@@screenshot_count}" + ".png"
File.delete($filename) if File.exist?($filename)
screenshot($filename)
@execute.call($filename)
end
def find_element(*args)
begin
result = super
result
rescue Exception => e
take_screenshot_when_fail
raise
end
end
...
...
end
module Appium
class Driver
prepend DriverExtension
end
end
采用流水线之后可以大幅度的减少人力成本,比如我们的客户之前还有安排同事专门负责构建打包上传等任务,代码出了问题也很难追溯到底在哪一步出了错。现在整个流程自动化了后,开发人员只需要更加专注于手中的开发任务,测试人员想什么时候测就什么时候测,要做的只需要点个按钮就好,是不是很赞!