设计动态变量参数(一)

160 阅读7分钟

「这是我参与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},说明参数关联有传递成功