第17章 一人一套测试环境
本章讲解第16次架构经历:一人一套测试环境。这是一个能够大幅提升团队开发测试效率的解决方案。

17.1 业务场景:测试环境何时能释放出来使用
当时公司的基础设施使用的是虚拟机,而且还未迁移到容器。公司一共搭建了3套测试环境:
- 一套专门用于联调
- 另外两套专门用于测试
之所以是3套而不是只有1套,主要是考虑到多个项目同时进行时需要分开测试和分开上线,而3套测试环境在一定程度上可以避免这些并行项目因为排队而导致延期的情况。
典型问题场景
之前有一个项目已经进入测试环节,功能测试反馈没问题后等待第三方验收,可是第三方的验收拖了很久,以至于不得不继续占用测试环境。
之后又有一个小的迭代项目要求一周后上线,并且还有一个上百人做的超大项目刚进入测试阶段,所以又需要两套测试环境。此时测试环境就不够用了,而且联调环境都被征用了。
抢环境的尴尬对话
甲:"我们有个紧急需求这周四要求上线,你们能不能把测试1让一下?"
乙:"不行,我们这个功能需要测试一周,下周四就要上线了。如果让给你们一天,我们就要延期一天上线了。"
甲:"其实是两天……"
乙:"那更不行了。要不你问问XX,他们在做的项目周期长,应该能让给你们两天。"
最终就是因为抢测试环境的问题,导致紧急需求上不了线。
17.2 解决思路
公司希望达成的目标是可以快速搭建一套新的测试环境,用完马上销毁。

针对这个目标,解决思路如下:
- 利用容器的特性,在几秒内快速启动服务实例
- 将测试环境需要搭建的服务通过容器实例部署起来
- 将这些容器通过Kubernetes管理(编排)起来
容器环境包含哪些服务
每套测试环境中需要部署的组件有MQ、ZooKeeper、Redis、配置中心、数据库、API服务、后台服务、网关等。
经过评估,如果把所有中间件都部署到容器中,将会出现以下问题:
- 中间件服务端改造成本大
- 客户端的SDK需要进行大量的改造
- 会导致容器环境与其他普通环境存在很大的代码差异
在容器测试环境中只部署独立的API服务或后端服务,其他组件直接重用测试环境的中间件。
需要解决的5个隔离问题
基于容器环境复用测试环境组件的设计,需要解决:
| 问题 | 描述 |
|---|---|
| API服务间的隔离 | 确保容器环境的请求能到达容器的API服务 |
| 后台服务间的隔离 | 确保容器服务只调用容器服务 |
| MQ和Redis隔离 | 确保消息和缓存不互串 |
| 配置中心数据隔离 | 确保各环境配置独立 |
| 数据库间的数据隔离 | 确保测试数据和结构兼容 |
17.2.1 API服务间的隔离

设计方案:每一个API服务中都会带一个配置项channelID,然后客户端每次访问API时都需要加上一个channelID参数;网关层接收到这个请求后,会根据channelID将请求匹配到对应channelID的API服务中。
具体流程:
- 每个项目都有一个JIRA Issue(如XXX123)
- 项目组会为每个项目单独创建一套容器测试环境
- 这个Issue ID自然而然地被当作了环境标识
- 在容器环境打包API服务时,自动将channelID的配置值改为JIRA Issue ID
- 网关层根据不同的channelID将请求分发到不同的API服务中
17.2.2 后台服务间的隔离
设计方案:
- 在打包RPC服务时,将一个环境变量
env的值设置为容器测试环境的标识(JIRA Issue ID) - 每个RPC服务注册ZooKeeper时,将在Service的metadata中加一个
tag参数,并设置tag的值为Issue ID - RPC服务只会调用同样tag的服务
示例:测试环境中有3个UserService:
- 一个是测试环境的虚拟机(tag为空)
- 两个是容器测试环境部署的UserService(tag值分别为XXX123和XXX245)
OrderService调用UserService时,如果OrderService也是XXX123这个容器环境的服务,则它只会调用带XXX123这个tag值的UserService。
17.2.3 MQ和Redis隔离
最终认为没必要专门定制,只需保证走测试流程时使用不同的测试数据就可以了(不同的项目一般都会使用不同的测试数据,包括不同的用户、不同的订单等),这样基本不会再出现不同容器测试环境流转相同MQ消息、缓存数据的情况。
尽量减少容器测试环境与正式环境的代码差异。
17.2.4 配置中心数据的隔离
设计方案:如果容器测试环境的值与虚拟机测试环境的值不一样,不会修改配置中心的值,而是在容器环境的启动脚本中动态加上针对各自容器测试环境的环境变量,然后在业务代码中启动环境变量优先级高于配置中心的参数。
17.2.5 数据库间的数据隔离
数据库互相影响的情况一般有两种:
1. 测试数据互相影响
和MQ、Redis的情况一样,只需要保证测试数据各自独立即可。
2. 数据库结构兼容问题
比如同时进行两个项目:
- XXX123这个项目删除了user表的updateFlag字段
- XXX100这个项目还需要使用这个字段
每次版本迭代时,都需要保证数据库可以兼容前一个版本的代码。不能直接删掉字段,而是等相关项目上线后再删掉。
如果无法兼容,可以将两个项目部署到不同测试环境的数据库中。
17.3 使用流程

每次新建一个工程时(新的API或者后台服务)都会在Jenkins上配置一个Job,而这个Job需要接受以下3个参数:
| 参数 | 说明 |
|---|---|
| Branch | 需要部署的代码分支 |
| 测试环境 | test1/test2/test3,决定使用哪个测试环境的中间件 |
| 容器测试环境标识 | JIRA Issue ID |
Job执行流程:
- 调用一个小工具连接Kubernetes
- 创建namespace(= JIRA Issue ID)
- 在namespace中增加一个pod(运行专门为JIRA Issue ID打包的代码)
项目级联动:假设XXX123需要使用UserAPI、UserService、OrderService、ProductService:
- 配置一个新的Jenkins Job来联动这些服务的Job
- 将各个服务对应的Branch、测试环境和JIRA Issue ID传入
- 每次点击这个项目的Jenkins Job时,就可以对其容器测试环境进行部署
如果项目成员想自己部署一套环境,只需单独配置一个新的Jenkins Job,并找一个不一样的(比如开发任务的Issue ID)容器测试环境标识即可。
17.4 小结

一人一套测试环境的方案成本其实非常小:
- 代码改动很少
- 一两周就可以把整个方案实施完成(时间主要用在申请服务器和部署Kubernetes上)
方案优势
此方案上线后,得到了使用者的一致好评,尤其是测试人员,主要原因:
| 优势 | 说明 |
|---|---|
| 无需协调 | 再也不需要因为协调测试环境花很多时间沟通了 |
| 一键部署 | 一键就可以将相关服务部署起来,不再需要一个服务一个服务地部署 |
| 提前介入 | 开发人员每完成一个功能,测试人员即可介入测试,而不需要等整个项目提测后再介入 |
总体来说,这个项目的效果非常好,之后的容器测试环境基本上保持人均一套的使用状态。
到这里,16次架构经历也就讲完了。接下来的结束语,不讲架构经历,将通过3次真实的经历分享:如何成为不可或缺的人。