You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 22设计实战: 一个全周期自动化测试Pipeline的设计
你好,我是柳胜。
说起交付流水线你可能立马想到的是Jenkins或CloudBees这些工具它们实现了从Code Build到最终部署到Production环境的全过程。
但Jenkins只是工具一个Pipeline到底需要多少个Job每个Job都是什么样的这些问题Jenkins是回答不了的需要使用工具的工程师去思考去设计。在实践中通常是DevOps工程师来做这个设计。
既然我们学习了微测试Job模型也知道了它能帮助我们去做自动化测试设计那用这个模型能不能帮我们做Pipeline设计呢**其实Pipeline本质上也是一个自动化测试方案只不过它解决的场景是把软件从代码端到生产端的自动化。**
掌握设计思维是测试工程师向测试架构师的必由之路假设今天你需要设计一个Pipeline把一个Example Service的代码最终部署成为生产环境的一个服务进程。完成这个工作你不仅能弄明白CICD的原理和实现而且对自动化测试Job怎么集成到CICD也将了如指掌。
![图片](https://static001.geekbang.org/resource/image/fc/bd/fc734fc77fcac6773de057d5d7437dbd.jpg?wh=1920x791)
## Example Service的Pipeline的目标
还是按照Job模型的设计原则先理出Pipleline的第一个根Job。
Pipeline的起点在哪里呢很显然它从一个GitLab的repo开始准确来说要能访问Example Service的代码。怎么能访问代码呢我们需要知道两个信息代码仓库的URL地址和访问的token。
1.GitLab的代码仓库地址[http://gitlab.example.com/sheng/exampleservice.git](http://81.70.254.64/yuzi/dockerbase.git)
2.访问tokentoken-123456
Pipeline的终点到哪里呢很简单最后Example Service能够在生产环境运行起来运行起来的标志就是Example Service的工作URL[http://prod.example.com](http://prod.example.com)
![图片](https://static001.geekbang.org/resource/image/c0/09/c05f9271d3a8dc72d8e16628d5690f09.jpg?wh=1920x791)
## 划分Pipeline的阶段
这个从代码转成生产环境服务的过程,至少要完成两次转换。
第一次转换是**把代码转成可部署包**,第二次是**把可部署包转换成运行态服务**。为了把好验证关的大门我们还要在这两次转换中间再加入一个测试任务那么Pipeline就可以分解成3个子Job分别是开发Job、测试Job、部署Job它们依次完成全部运行通过最后才能部署到生产环境。如下图
![图片](https://static001.geekbang.org/resource/image/a6/bb/a698bd4c9cbe0912fe9f970c4de5fabb.jpg?wh=1920x791)
这三个Job我们依次来看一下它们都需要干什么活。
首先我们来看看DevJob。开发阶段测试Job作为整个Pipleline的初始JobPipeline的Input就是DevJob的Input。
1.GitLab的代码仓库地址[http://gitlab.example.com/sheng/exampleservice.git](http://81.70.254.64/yuzi/dockerbase.git)
2.访问token token-123456
DevJob要做什么呢它前面连接了代码库后面要对接集成测试它的主要职责就是把代码变成可部署的Package。
Input有了DevJob要输出什么呢我们要知道DevJob的输出会作为下一阶段Job的输入设计DevJob的输出就跟接口设计一样不同的接口都能让系统工作但我们要选择最合适那一种。
比如我们可以让DevJob输出一个Example Package的仓库地址代表Package已经打好了并上传到了仓库或者我们输出单元测试报告甚至DevJob也可以什么都不输出让下一个TestJob自己去按照双方约定好的Ftp服务器上去取包。
哪种方案更合适呢?
这里你要考虑两点第一我的输出别人是否用得到像单元测试报告这种信息是我DevJob内部的工作产物别人不会感兴趣。按照面向对象设计原则这些细节应该隐藏在DevJob内不向外公布。
第二我的输出是不是信息显式而充分的像多个Job都遵循一个约定去指定的FTP服务器上去取放Package这个信息交互就是隐式的没有在接口上体现出来。这样是有隐患的如果之后各个Job变更重构的时候就会出问题。
根据这个原则我们让DevJob输出一个Example Package的仓库地址
DevPackageURL: [http://nexus.exmaple.com/content/repositories/exampleservice\_1.0.1.jar](http://nexus.exmaple.com/content/repositories/exampleservice.jar)
如果DevJob输出了这个URL就代表DevJob已经运行通过把代码变成了Package而且把Package放到了组件管理平台Nexus上去别人就可以用了。
你可能会问拿到了Package URL还需要Nexus的用户名密码才能访问。那下一个Job从哪里获知用户名和密码呢这时就可以用到我们Job测试模型的TestConfig属性了。直接把用户名、密码放在TestConfig就可以了。
![图片](https://static001.geekbang.org/resource/image/d1/82/d170896468d28932a5eb07745fe79482.jpg?wh=1920x755)
同样的思路我们也能整理出TestJob的Input是DevPackageUrlOutput是ReleasePackageUrl
![图片](https://static001.geekbang.org/resource/image/22/b4/22ca2574e08a1622293e08bcfdc250b4.jpg?wh=1920x755)
DeployJob的Input是ReleasePackageUrl作为Pipeline最末一个JobPipeline的输出就是DeployJob的输出
![图片](https://static001.geekbang.org/resource/image/85/b2/850eac96f93e65yy59d0124cac813db2.jpg?wh=1920x755)
推演到现在整个Job树就成了后面这样
![图片](https://static001.geekbang.org/resource/image/d0/35/d08104f939e9ff47096974d761395935.jpg?wh=1920x713)
我们把Pipeline拆分成Dev、Test和Deploy三个Job后就可以把它们分别指派给开发人员、测试人员、运维人员由他们来推进下一阶段的设计了。至此概要设计告一段落。
## 各阶段Job的详细设计
我们在上面的设计中规划了3个阶段明确了作为Pipeline job的第一级子JobDevJob、TestJob、DeployJob。但每个阶段要做什么具体任务还需要进一步详细设计。
### DevJob
我们先看DevJob它的Output是一个可执行包。
怎么完成从代码到可执行包的转换呢当然要进行构建也就是Build。
BuildJob的模型很容易梳理它的Input是codeRepoUrl而Output是DevPackageUrl。BuildJob从代码库里抓取代码构建然后打包生成Package上传到对象仓库就完成了。
但build通过了后是不是可以直接产生DevPackage呢这里我分享一个“保险”措施为了保证质量我们可以在build之后加上一个测试任务也就是单元测试UnitTestJob。单元测试通过之后再输出DevPackage。
UnitTestJob就是运行单元测试单元测试成功还是失败就是UnitTestJob的结果。所以UnitTestJob的Job模型里只设置一个Dependency就好了指向BuildJob。BuildJob成功之后才能运行UnitTestJob。这里的UnitTestJob我们选用Junit开发框架就能实现。
现在DevJob被细分成了两个子JobBuildJob和UnitTestJob。
按照Job模型的规则子Job都运行成功父Job才能成功。所以BuildJob和UnitTestJob运行成功DevJob才算成功而DevJob成功了后面的TestJob才能触发运行。
![图片](https://static001.geekbang.org/resource/image/26/cd/26d9e9dd80fbc153542b8553b28874cd.jpg?wh=1920x1038)
### TestJob
经过第二模块里策略篇的学习我们知道了测试的策略有单元测试、接口测试和系统测试。单元测试一般是开发人员来做已经包含在DevJob里了。而接口测试和系统测试就需要放在TestJob里。
先来看APITestJob的模型。APITestJob作为TestJob的第一个子JobAPITestJob的Input就是它的父Job TestJob的Input也就是DevPackageUrl。有了这个信息APITestJob就可以开发实现了它的自动化工具可以选用RestAssure。
然后是E2ETestJob的模型它的Dependency是APITestJob而Output是ReleasePackageUrl。至于E2ETestJob的自动化技术我们可以选用Selenium。
现在可以把TestJob分解成了2个子JobAPTestJob和E2ETestJobJob树如下
![图片](https://static001.geekbang.org/resource/image/de/89/dede1f23e6df7yyfb231e2b5b2702f89.jpg?wh=1920x1038)
### DeploymentJob
又下一城咱们一鼓作气继续来看DeploymentJob。
当Pipeline移动到DeploymentJob的时候它会检查前置依赖TestJob的状态只有成功了DeploymentJob才会运行。在运行的时候DeploymentJob会从Input获得一个ReleasePackageUrl然后经过一系列部署操作把Package部署到生产环境里开始运行输出一个serviceUrl。
为了完成这个过程简单的做法是DeploymentJob不需要再细分子Job你可以直接把它当作一个可执行Job用部署脚本来实现。
## 转成自动化测试开发计划
搞定上面的设计后现在Job树变成了这样
![图片](https://static001.geekbang.org/resource/image/51/8b/51a0de00fc0c6e5e8e76e98a3b1b528b.jpg?wh=1920x1087)
现在每一个叶子结点就是可执行的Job需要自动化实现。
![图片](https://static001.geekbang.org/resource/image/74/78/741517d2175c0ee8a4e648e811638e78.jpg?wh=1920x693)
## 变更和扩展
在Pipeline搭建起来后我们还要持续维护。这里有两种情形一种是新增Job还有一种是扩展Job。
先看新增Job的场景我给你举个例子。比如开发人员有一天要引入Sonar做代码质量检测那我们该怎么做呢
动手前我们还是先分析一下。Sonar扫描也是基于代码CodeRepoUrl这个和DevJob的Input一致。这样我们可以建立一个SonarJob作为DevJob的子Job可以复用DevJob的Input。
那SonarJob和BuildJob还有UnitTestJob是什么关系呢这就看我们想怎么定义它们的依赖关系了。也就是谁在前端谁在末端。
一般来说代码扫描应该发生在代码变更的最早时刻。一旦有代码提交就首先检查代码的质量如果代码质量不符合预定标准就终止Pipeline。实践里这样选择是为了优先保证代码的质量。
基于这样的目标我们就把SonarJob添加到DevJob下的第一个子Job让BuildJob依赖SonarJob而UnitTestJob保持不变还是依赖BuildJob。
另外还有一种情形就是某一个Job当时设计实现比较简单后来需要扩展。比如DeploymentJob刚开始我们用一段Shell脚本就可以轻松执行它。但随着Deployment要求提高我们需要在Deployment结束后还要执行测试任务验证质量然后再发布serviceUrl。面对这种情况我们该怎么办
其实这个场景,就跟开发一样,一旦代码规模扩大了,就需要分拆模块了。
在我们的Job树里原先的DeploymentJob需要分拆了拆分成ExecuteDeployment和TestDeployment让TestDeployment依赖于ExecuteDeployment。之前的DeploymentJob变成一个抽象Job它的脚本实现挪到ExecuteDeploymentJob里在新TestDeployment里加上测试任务即可。如下图
![图片](https://static001.geekbang.org/resource/image/8b/96/8bc413d8d91f669927efde462fc77d96.jpg?wh=1920x1268)
## 小结
为了检验咱们的Job模型威力几何今天我们再次锻炼设计思维进行了部署Pipeline的设计。经过案例演练你现在是不是对Job模型更加熟悉了呢
咱们回顾一下关键流程。概要设计里我们完成了抽象Job的定义对应着Pipeline划分的三大阶段开发阶段、测试阶段和部署阶段然后逐层细化一直到实例Job。而在详细设计里我们不仅要确定实例Job的接口而且还要确定它们的自动化测试实现技术。
做完设计每一个叶子节点的Job都是要去开发实现的自动化测试任务。我们就可以整理出一个自动化测试开发任务列表了。
后面是Job树叶子工作表供你复习回顾。
![图片](https://static001.geekbang.org/resource/image/74/78/741517d2175c0ee8a4e648e811638e78.jpg?wh=1920x693)
一个优秀模型,不光着眼于眼前的需求,还具备应对未来变化的能力。除了概要设计和详细设计,我们还推演了未来的应变策略。
当规模扩大时原先的可执行Job的逻辑变得复杂的时候我们依然可以根据Job模型把可执行Job再进行分解成多个子Job。需要注意的是我们遵循的原则是只有叶子结点Job才是可执行Job不再进行分解的Job而一旦Job下挂了子Job那这个Job就变成了抽象Job只负责接口的定义。
今天讲了Pipeline设计也是对前面Job设计内容的复习巩固。接下来我们就要进阶到另外一个难度级别的设计了不仅有多端协作还有分布式事务这种场景怎么设计我们下讲见。
## 思考题
你负责或者参与的Pipeline原先是怎么设计的看完这一讲你想到怎么优化它了么
欢迎你在留言区跟我交流互动。如果觉得这一讲对你有启发,也推荐你把它分享给更多朋友、同事。