「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」。
前言
题主在思考如何写本篇内容的时候,在想一个问题:如何写才能讲清楚为啥要设计动态变量参数?这好像是病句,变量和参数能混为一谈?
更正一下:动态变量;参数作何解?发送请求时的参数,这个参数需要是动态变化的,而不是写死的硬编码数据。
- 抓住重点,什么是变量?
变量是可以通过变量名访问的,在编译执行程序时,它是可变的。
- 那么问题来了?
咱们需要强调一点的是,在接口或UI自动化测试的时候,实际是在干嘛?是在模拟用户请求咱们的服务器,那么对于用户输入的数据是唯一的吗?
当然不是,一千个读者就有一千个哈姆雷特,嗯,这个比喻不太恰当,但意思就是这么个意思,在测试过程中尽量避免使用硬编码的方式来执行脚本。
- 所以需要解决这一问题:如何在接口自动化测试中灵活使用测试数据?
举个例子1
以登录接口为例,根据接口文档,设计接口测试用例,不外乎从入参着手,有成功就有失败,有失败就有各种各样的失败,于是用例数据就产生了⬇⬇⬇
{"deviceInfo":{"osVersion":"14.6","appVersion":"1.5.0","deviceName":"iPhone 8 Plus","channel":"appstore","deviceId":"F80BFE87-B87B-49B1-BA43-A3031C30A863","deviceType":"ios"},"offsetHour":8,"zone":"86","mobile":"13800138000","code":"1234"}
{"deviceInfo":{"osVersion":"14.6","appVersion":"1.5.0","deviceName":"iPhone 8 Plus","channel":"appstore","deviceId":"F80BFE87-B87B-49B1-BA43-A3031C30A863","deviceType":"ios"},"offsetHour":8,"zone":"86","mobile":"A1380138000","code":"1234"}
{"deviceInfo":{"osVersion":"14.6","appVersion":"1.5.0","deviceName":"iPhone 8 Plus","channel":"appstore","deviceId":"F80BFE87-B87B-49B1-BA43-A3031C30A863","deviceType":"ios"},"offsetHour":8,"zone":"86","mobile":"13800138000","code":"12354"}
{"deviceInfo":{"osVersion":"14.6","appVersion":"1.5.0","deviceName":"iPhone 8 Plus","channel":"appstore","deviceId":"F80BFE87-B87B-49B1-BA43-A3031C30A863","deviceType":"ios"},"offsetHour":8,"zone":"86","mobile":"13800138000","code":"A354"}
{"deviceInfo":{"osVersion":"14.6","appVersion":"1.5.0","deviceName":"iPhone 8 Plus","channel":"appstore","deviceId":"F80BFE87-B87B-49B1-BA43-A3031C30A863","deviceType":"ios"},"offsetHour":8,"zone":"A86","mobile":"13800138000","code":"1354"}
- 密密麻麻,简单点,就给每组数据取一个名字,一一对应
登录成功
手机号格式不正确
验证码长度错误
验证码格式不正确
区号必须为数字
- 即每组数据请求得到的结果是成功1条,剩下的都是失败、各种各样的失败。
这样的数据没毛病,可以ddt但是不需要动态替换参数,为啥呢?
因为是登录接口,业务决定了它不需要参数化各种数据,还有就是设计用例的方法:边界值、等价类划分等等。
那咱换一个接口,以注册接口为例。
注册的接口的参数,可就要灵活得多了,必须是要动态的,
原因有二:重复的手机号注册必定是失败的;所以每次请求必须为未注册的手机号。
那怎么确定它是最新的呢?这是第二个问题了。
- 先看注册接口的测试用例代码
class Test(unittest.TestCase):
data = {"mobile":13800138000, "code":1234}
headers = {"content-type":"application/json"}
def test_register(self):
url = "http://xxxxx/api/register"
requests.post(url, json=self.data, headers=self.headers)
# 示例先忽略断言
- 硬编码就是这么直接,所以每次请求需要手动换掉手机号码,但是又不建议每次手动修改(自动化测试,避免的就是手动参与的方式)
class Test(unittest.TestCase):
headers = {"content-type":"application/json"}
def setUp(self):
self.mobile = "138" + str(random.randint(10000000,99999999))
def test_register(self):
data = {"mobile":self.mobile, "code":1234}
url = "http://xxxxx/api/register"
requests.post(url, json=data, headers=self.headers)
# 示例先忽略断言
# 也可以是这样,写在测试用例类或其他工具类中,
def generate_mobile(self):
# 如何生产一个不重复的手机号
pass
- 意思呢,大概就这么个意思,每次请求注册,保证手机号不一样;如何保证?引出第二个问题⬇⬇⬇
无论你怎么随机总会有概率出现重复的手机号,只是概率大小而已,唯一可能的法子就是在请求注册之前去数据库中比对mobile是否存在,但这个效率可能会低一些,毕竟有个数量在那里,比对就不必了,给个小窍门:咱先查出数据库中mobile最大的值,目前大陆手机号是11位,既然查出数据库中最大的mobile,咱们就给它+1使之比它更大,那么它就不存在于数据库中,也就能注册成功且每次都能成功。
# 后面会讲到这个python关于mysql的操作方法
select max(mobile) from user;
举个例子2
上面的例子是单接口的,如果是多接口呢?
咱们这次要说的是参数关联,它也是参数化的一种方式,多接口,咱们理解它是基于业务场景的接口测试,且参数存在联动关系(参数依赖)。
- 请求接口1得到响应结果某id是接口2请求参数的入参1
这话说着有点绕,咱们直接看代码案例(未实现参数关联)
class Test(unittest.TestCase):
headers = {"content-type":"application/json"}
def setUp(self):
self.mobile = "138" + str(random.randint(10000000,99999999))
def test_register(self):
data = {"mobile":self.mobile, "code":1234}
url = "http://xxxxx/api/register"
requests.post(url, json=data, headers=self.headers)
# 示例先忽略断言
def test_get_userInfo(self):
data = {"user_id":1} # 按理用户id是由上一个接口的响应参数得到的
url = "http://xxxxx/api/get_userinfo"
self.headers["Authorization"] = "**************************************" # 当然还有token权限认证相关
requests.get(url, json=data, headers=self.headers)
先分析一波
先不管前面是作何解释(自动化),但是对于测试数据还是处于硬编码阶段,如果需要换不同的数据,则需要手动去修改;
如此一来却不够灵活;所以需要设计用例变量,提高参数灵活性。
- 既然问题已知,那么就能解决(手机号这个先如此处理):假设注册手机号返回的数据格式如下:
{
"code": 0,
"codeDesc": "string",
"detailDesc": "string",
"result": true,
"value": {
"userId": 123
}
}
- 第一步需要提取register接口返回的userId字段,作为下一个接口的入参;
之前在给同事培训的时候,他们对于参数关联有很多的疑问,python中如何往下传递参数,又是一顿参数和变量的讲解,弄得大家晕头转向; 这里咱先给一个简单的解决方案,global全局变量
class Test(unittest.TestCase):
headers = {"content-type":"application/json"}
def setUp(self):
self.mobile = "138" + str(random.randint(10000000,99999999))
def test_01_register(self):
"""新用户注册"""
global userId
data = {"mobile":self.mobile, "code":1234}
url = "http://xxxxx/api/register"
# res = requests.post(url, json=data, headers=self.headers).json()
# 示例先忽略断言
res = {
"code": 0,
"codeDesc": "string",
"detailDesc": "string",
"result": True,
"value": {
"userId": 123
}
}
userId = res.get("value").get("userId")
def test_02_getUserInfo(self):
"""获取用户信息"""
# 这里演示全局变量引用
data = {"user_id": userId} # 按理用户id是由上一个接口的响应参数得到的
print(data)
url = "http://xxxxx/api/get_userinfo"
self.headers["Authorization"] = "**************************************" # 当然还有token权限认证相关
requests.get(url, json=data, headers=self.headers)
if __name__ == '__main__':
unittest.main()
# 在控制会输出:{"user_id": 123},说明参数关联有传递成功