一款需要正式对外发布的产品,通常都需要经历一个较完整的测试验证过程,在整个产品质量验证阶段,一般会经历几类测试环境的验证:从产品集成阶段的测试环境->验收阶段的预发布环境->正式发布回归的生产环境。
由于不同环境之间或多或少存在一些差异性,为了能将这些环境差异性导致的问题充分暴露出来,测试人员需要在这些不同的环境中都要进行必要的测试验证。
接口自动化测试作为质量保障的一种手段,除了用在测试阶段,也需要用在预发布环境和生产环境。
很多时候,为了能让测试用例运行在多套环境中,不得不维护多套测试脚本、测试用例。这种方式虽然可行,但会造成大量的测试用例、测试脚本冗余,以及巨大的后期维护工作量。
那么有没有一种方式或者说实现策略,可以实现一套接口测试用例可按照特定测试需求运行在多套环境中呢?答案是肯定的。
接下来,就带着大家,分别从测试框架和语言实现两个层面介绍如何实现一套测试自动化用例脚本运行在多个环境下。
相信很多读者能感受到一个明显现象,公司规模越大,对各类环境的定义也会更加清晰、明确,环境种类也会进一步的细分。这么多环境的加持下,对自动化测试实施过程提出了一个挑战或者说是需求:自动化用例应当支持在不同环境里执行,并且对用例逻辑层透明无感。
为了实现诸如此,有些人,采取了较为” 傻瓜式 “的方式,拿下面这段代码为例。
def test_login(self):
if env = "dev":
requests.post("https://dev.xxx.com/login", data={"username":"dev", "password":"123456"})
do_something()
elif env = "test":
requests.post("https://test.xxx.com/login", data={"username":"test", "password":"123456"})
do_something()
elif env = "pre":
requests.post("https://pre.xxx.com/login", data={"username":"pre", "password":"123456"})
do_something()
else:
requests.post("https://www.xxx.com/login", data={"username":"superadmin", "password":"123456"})
do_something()
看完上述代码写法,有没有似曾相识的同学,如果有的话,很不幸地告诉你,你采取了最不为推荐的方法。上述示例还仅仅只是一条用例,如果自动化测试用例体量大 (实际都不会小),自动化测试用例脚本维护、代码重复量带来的灾难性可想而知。
仔细分析一下,要实现一套测试用例在多环境下执行,要解决哪些问题:
- 不同环境的服务入口地址不同,一般还会有 http/https 的差别
- 不同环境需要使用不同的测试数据
- 一些中间件,比如数据库、消息队列、缓存服务的访问地址、账号、配置有差别
- 不同环境的第三方回调地址有差别
- 不同环境的配置需要整体切换,不能出现在测试环境里用了生产环境的数据的问题
以上都是些常见的问题,实际不同公司下,有些业务功能在实现上都还存在差异,不过这种就不在我们讨论的范围内了。
针对上述的这些主要问题,归纳总结一下,不难发现,在不同的环境中,对于同一个接口测试来说,测试过程的逻辑基本都是一样的,而造成不同环境用例无法复用的因素最主要有两个:
① 所调用到的服务域名地址不同,不同环境对应的域名是不同的。比如测试环境的域名为 test.xxx.com,而正式环境的域名对应为 www.xxx.com。
② 测试数据、配置数据不同,不同环境对应的测试数据和用到的配置数据存在不同。比如测试环境对应的用户 ID 为 123456,但在正式环境对应的用户 ID 可能就变成了 654321。
而针对不同环境,调用的服务域名地址不同,解决该问题的基本思路用两个关键词概括:抽象、枚举。
如何抽象,如何枚举,下面分别从测试框架(以 Robot Frameowork 框架为例)和语言实现层面(以 Python 语言)为大家逐一介绍。
1. 测试框架支持多环境运行思路
下述以 Robot Framework 框架为例,介绍如何实现一套测试用例支持多个不同运行环境,不同框架实现思路皆相通,其它框架可供参考借鉴。
在 RF 框架下,实现此类需求,总的原则是利用:外部变量文件 + 全局动态变量,将接口测试脚本中涉及传入域名的值统一封装抽离为一个统一的公共环境变量,并且将各个不同环境域名统一存放到一个公共环境配置变量文件中。
先来看一则脚本片段截图:
可以看到,在调用 request_post 关键字发起 POST 请求时,需要传入域名地址 ${URL}、接口路径 ${path}、接口参数 ${datas}等。而对于同一个接口,不管是在哪个环境下,接口路径 ${path}和接口参数 ${datas}都应该是一样的。而对于接口地址 ${URL},在不同环境中对应的值会有所不同。但从图中我们并没有发现 ${URL}变量定义的位置,它的值是从哪里传进来的呢?
关于接口地址 ${URL}变量值动态引入,通常有两种方式。
- 通过外部变量文件引入。
- 通过全局动态参数引入。
1.1 通过外部变量文件引入
(1)定义好接口测试实战项目的目录结构,在 Resource | Lib 目录下,创建环境配置变量文件,文件名称定义为 config.py,文件内容如下:
# coding=utf-8
# 环境配置文件
# 测试环境
# URL = 'https://test.xxx.com'
# 预发布环境
# URL = 'https://pre.xxx.com'
# 生产环境
URL = 'https://www.xxx.com'
在 config.py 环境配置文件中,定义各个不同环境(测试环境、预发布环境、生产环境)的服务域名地址,且变量名统一为 URL。在运行接口测试时,保留当前需要运行测试用例的环境地址,其他环境变量注释掉即可。
在实际项目当中,config.py 配置文件中的地址替换成真实的接口服务地址即可,例如,上述配置文件中保留了生产环境的地址,此时运行接口测试用例,则调用的为生产环境的接口测试。需要注意的是,在同一个项目下,不同环境下的接口服务地址需要采用相同的变量名称,定义好后,在 Robot Framework 测试脚本中直接通过 ${URL}变量形式来引用环境变量值。
(2)环境配置变量文件创建好后,选择 Resource | Business| 业务资源文件,在资源文件 Settings 配置选项中选择 Add Variables 添加变量文件,依次选择 config.py 配置文件存储路径,如图所示。
(3)config.py 变量文件导入成功后,当需要在不同环境下运行接口测试用例时,可在用例脚本不做任何变更的情况下,只需要更改 config.py 配置文件中的地址即可实现一键切换接口测试运行环境。
1.2 全局动态参数引入
通过外部变量文件的形式引入,虽然可以实现在测试脚本不做任何变更的前提下完成一套用例多套环境运行的目的,但每次在不同环境运行时,需要去环境变量文件中进行调整,虽然调整幅度较小(只需要进行注释),但仍然不是那么便捷。
在 Robot Framework 中还在一种更便捷灵活的方式来实现此目的,即通过全局参数变量引用形式来实现对应变量值的全局动态修改。而采用参数变量引用的形式来实现变量值的动态修改,也分为两种方式。
1) 第一种方式:Arguments 参数栏
在 RIDE 编辑器 Run 运行标签下的 Arguments 参数栏中增加参数变量--variable key:value
。如下图所示,增加了一个变量名为 URL,变量值为https://test.xxx.com
。
参数栏中增加变量的书写格式:
-v变量名:变量值或者--variable变量名:变量值。
上图中,参数栏填入-v URL:https://test.xxx.com
,对 URL 变量赋值为https://test.xxx.com
。这样在运行接口测试用例时,会将 URL 对应的变量值动态修改赋值为https://test.xxx.com
。此时即使环境变量文件中的 URL 变量为https://www.xxx.com
。通过这种命令行参数变量的引入形式仍然可以实现动态修改 URL 值。
通过参数变量--variable key:value 形式引入的变量值,为全局变量优先级最高。
2) 第二种方式:命令行参数
采用 Pybot 或 Robot 命令行的形式来运行 Robot Framework 接口测试用例时,引入参数变量替换,例如:
Robot --variable URL:"https://test.xxx.com" /usr/local/rf_api || exit 0。
此种方式也是最为常用的调用形式,适合与 CI 持续集成系统对接。
2. 语言层面支持多环境运行思路
以 Python 语言为例,从语言层面解决如何一套用例支持多环境运行,本质还是要在用例层对测试环境无感,需要把环境所用的数据抽象出来。
随便画了一张草图,大家凑合着看,图中所示,是一个典型的桥接模式(Bridge Pattern):将抽象部分与实现部分分离,使它们都可以独立地变化。
拿上述最开始的代码示例来讲:需要抽象出服务地址
、账号
两个对象,用例逻辑层只允许使用这些抽象的对象,而不能直接访问具体的数据,例如改成如下:
def test_login(self):
requests.post(entrypoint.URL+"/login", data={"username":data.account.username, "password":data.account.password})
而在 Python 中,可以利用 property 装饰器来实现简易的桥接模式(其它语言也有类似的设计模式或实现),代码示例如下:
from enum import Enum
class Environment(Enum):
DEV = 0
TEST = 1
PRE = 2
PROD= 3
class EntryPoint:
_ENV_URL = {
Environment.DEV: "https://dev.xxx.com",
Environment.TEST: "https://test.xxx.com",
Environment.PRE: "https://pre.xxx.com",
Environment.PROD: "https://www.xxx.com"
}
@property
def URL(self):
return self._ENV_URL[env]
env = Environment.DEV # 作为全局的环境变量
样例代码中,先通过继承 Enum 类实现了一个枚举类 Environment,在枚举类中定义了各环境的常量,可以理解是为后续定义各环境具体的 Key 值。
接着定义了一个 EntryPoint 类,并且在该类中,定义了一个存储各环境的字典,KEY 名为枚举类中定义的常量。通过在 URL 方法 ,增加@property装饰器,可以让 URL 方法变成只读属性,并且通过 obj.URL 即可调用。
如果需要切换环境去执行,只要更新全局变量env
就可以实现。
如果你对 Python 中的,Enum枚举用法
和@property
装饰器,还不了解或者想更深入了解这些用法,具体可参考官方文档介绍。
# @property用法官方文档
https://docs.python.org/3/library/functions.html#property
# Enum用法官方文档
https://docs.python.org/zh-cn/3/library/enum.html
受篇幅限制,一套自动化测试用例,不同环境对应的测试数据不同,解决思路下回再介绍,完整的自动化测试设计规范及各类实战技巧,建议可以系统性地学习:《自动化测试实战宝典:Robot Framework + Python 从小工到专家》一书中的内容。 新书京东订购传送门