zoukankan      html  css  js  c++  java
  • apiAutoTest:自动化测试用例中调用自定义函数的实现

    0. 前言

    apiAutoTest从去年8月以来开源至今,也更新了不少内容,一起来看看吧

    • 第一个版本

      - 2020/08/08 增加实际响应存储数据的方法,并在字典可以处理依赖见tools/svae_response.py
      
      - 2020/08/09 实现多文件上传,接口中Path参数依赖处理
      初步实现用迭代的方式来处理接口中的数据依赖关系
      
    • 第二个版本

      - 2020/11/18 使用re库替换之前的字典迭代方式来处理数据依赖
      - 2020/11/21 config.yaml文件中新增基准header设置
      - 2020/11/22 支持预期结果中字段断言时使用语法糖来实现动态字段断言
      - 2020/12/08 优化断言信息,增加数据库(支持mysql)查询操作, 使用`@pytest.fixture(scope="session")`来托管数据库对象,用例新增sql栏
      - 2020/12/16 使用conftest.py 初始化用例, 增加失败重跑机制, 增加运行文件run,优化test_api.py冗余代码
      
    • 第三个版本

      - 2021/01/19 添加数据清洗功能(测试开始前进行数据库备份-分别在服务器和本地进行,测试结束后将备份用以恢复数据-将尝试从服务器和本地恢复到服务器数据库中,docker部署的mysql服务已本地调试通过,直接linux部署的mysql并未测试)
      - 2021/02/27 添加hooks.py文件(可在此处自定义方法,并用于用例当中,注意请务必在定义的方法中使用return),移除上次更新的eval语法糖,增加用例处理前的日志
      

    一度说不会再更新维护代码,结果还是慢慢的更新了...

    1. 自定义函数实现的故事

    这是今天更新的,主要需求来自一个apiAutoTest的学习者反馈,这里感谢他,在此之前另一个小伙伴说他需要用上个接口返回的id字段进行运算, 很多测试框架都有这个功能,但我给apiAutoTest的定位是个工具,也就造个轮子嘛

    2. 用例中如何使用自定义函数

    2.1 在tools/hooks.py中定义好函数

    def get_current_highest():
        """获取当前时间戳"""
        return int(time.time())
    
    
    def sum_data(a, b):
        """计算函数"""
        return a + b
    

    2.2 在用例中如何使用该函数

    语法糖: @函数名()@: 使用无参数函数

    @函数名(参数1, 参数2)@: 向函数传递参数

    ps: 函数参数兼容apiAutoTest中的提取依赖语法&此处为jsonpath语法&

    用例(怕截图不清所以就这里简易模拟了两条)

    用例编号 用例标题 接口路径 是否执行 token操作 请求方式 入参关键字 上传文件 请求数据 后置sql 预期结果
    case_001 post请求实现登录 login post data {"username": "admin", "password": "123456"} select * from user where id=&$.case_002.data.id&; {"$.meta":{ "msg": "登录成功", "status": 200 }}
    case_002 调试函数sum_data(),从path需要运算 users/@sum_data(&$.case_001.data.id&, 2)@/ put data {"username": "tery","password": @sum_data(&$.case_001.data.id&, 66)@, "timer": @get_current_highest()@, "timer_str": " @get_current_highest()@"} {"$.meta":{"msg": "设置状态成功", "status": 200}}

    运行日志(运行上述用例得到日志如下)

       
    2021-02-27 16:06:50.538 | DEBUG    | api.base_requests:send_request:34 - 用例进行处理前数据: 
     接口路径: login 
     请求参数: {"username": "admin", "password": "123456"} 
     后置sql: select * from user where id=&$.case_002.data.id&; 
     预期结果: {"$.meta":{ "msg": "登录成功", "status": 200 }}
    2021-02-27 16:06:50.765 | INFO     | api.base_requests:send_api:81 - 
    最终请求地址:http://www.ysqorz.top:8888/api/private/v1/login
    请求方法:post
    请求头:{'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'}
    请求参数:{'username': 'admin', 'password': '123456'}
    上传文件:None
    响应数据:{'data': {'id': 500, 'rid': 0, 'username': 'admin', 'mobile': '12345678', 'email': 'adsfad@qq.com', 'token': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MTQ0MTMyMTAsImV4cCI6MTYxNDQ5OTYxMH0.cZTYLARKNj8SKlPGPdIUh9RmyQaYAJnJrLObaKiNiU4'}, 'meta': {'msg': '登录成功', 'status': 200}}
    2021-02-27 16:06:50.773 | INFO     | tools.data_process:save_response:27 - 添加key: case_001, 对应value: {'data': {'id': 500, 'rid': 0, 'username': 'admin', 'mobile': '12345678', 'email': 'adsfad@qq.com', 'token': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MTQ0MTMyMTAsImV4cCI6MTYxNDQ5OTYxMH0.cZTYLARKNj8SKlPGPdIUh9RmyQaYAJnJrLObaKiNiU4'}, 'meta': {'msg': '登录成功', 'status': 200}}
    2021-02-27 16:06:50.775 | INFO     | tools.data_process:assert_result:115 - 第1个断言,实际结果:{'msg': '登录成功', 'status': 200} | 预期结果:{'msg': '登录成功', 'status': 200} 
    断言结果 True
    2021-02-27 16:06:50.775 | DEBUG    | api.base_requests:send_request:34 - 用例进行处理前数据: 
     接口路径: users/@sum_data(&$.case_001.data.id&, 2)@/ 
     请求参数: {"username": "tery","password": @sum_data(&$.case_001.data.id&, 66)@, "timer":  @get_current_highest()@, "timer_str": " @get_current_highest()@"} 
     后置sql:  
     预期结果: {"$.meta":{"msg": "设置状态成功", "status": 200}}
    2021-02-27 16:06:50.775 | DEBUG    | tools:rep_expr:45 - &$.case_001.data.id& 替换的值为 500 
    2021-02-27 16:06:50.775 | DEBUG    | tools:rep_expr:50 - 执行hooks函数sum_data(500, 2) 替换的值为 502
    2021-02-27 16:06:50.775 | DEBUG    | tools:rep_expr:45 - &$.case_001.data.id& 替换的值为 500 
    2021-02-27 16:06:50.783 | DEBUG    | tools:rep_expr:50 - 执行hooks函数sum_data(500, 66) 替换的值为 566
    2021-02-27 16:06:50.783 | DEBUG    | tools:rep_expr:50 - 执行hooks函数get_current_highest() 替换的值为 1614413210
    2021-02-27 16:06:50.783 | DEBUG    | tools:rep_expr:50 - 执行hooks函数get_current_highest() 替换的值为 1614413210
    2021-02-27 16:06:50.835 | INFO     | api.base_requests:send_api:81 - 
    最终请求地址:http://www.ysqorz.top:8888/api/private/v1/users/502/
    请求方法:put
    请求头:{'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MTQ0MTMyMTAsImV4cCI6MTYxNDQ5OTYxMH0.cZTYLARKNj8SKlPGPdIUh9RmyQaYAJnJrLObaKiNiU4'}
    请求参数:{'username': 'tery', 'password': 566, 'timer': 1614413210, 'timer_str': ' 1614413210'}
    上传文件:None
    响应数据:{'data': {'id': 502, 'username': 'linken', 'role_id': 34}, 'meta': {'msg': '更新成功', 'status': 200}}
    2021-02-27 16:06:50.835 | INFO     | tools.data_process:save_response:27 - 添加key: case_002, 对应value: {'data': {'id': 502, 'username': 'linken', 'role_id': 34}, 'meta': {'msg': '更新成功', 'status': 200}}
    2021-02-27 16:06:50.835 | INFO     | tools.data_process:assert_result:115 - 第1个断言,实际结果:{'msg': '更新成功', 'status': 200} | 预期结果:{'msg': '设置状态成功', 'status': 200} 
    断言结果 False
    2021-02-27 16:06:53.418 | SUCCESS  | __main__:run:43 - 报告已生成
    

    分析日志

    从上述日志可以看出语法糖@sum_data(&$.case_001.data.id&, 2)@运行之后的结果为502, 其处理的顺序则是先&$.case_001.data.id& 提取出来得到的值是500, 然后调用函数sum_data(500, 2),然后运行这个函数并把结果502 与@sum_data(&$.case_001.data.id&, 2)@进行替换.

    3. 自定义函数实现代码

    因为这里用例读取出来的内容是字符串,我影响中反射应该能做到这一点,然后就去找了下资料,然后我又找到getattr()这个内置函数但是这个函数不能解决用例带参数的函数问题,然后我把目光移到了exec()内置函数,该函数可以执行字符串的Python代码,然而我又遇到了问题,该函数里面执行的Python代码变量在其他函数中不能顺利取出来用,最后我找到了资料 在函数内部使用locals()得到一个局部变量字典,通过字典取值的方式 把exec中的变量 取出来

    tools/hooks.py

    #!/usr/bin/env python
    # _*_ coding: utf-8 _*_
    """
    @project: apiAutoTest
    @file: hooks.py
    @author: zy7y
    @time: 2021/2/27
    @site: https://cnblogs.com/zy7y
    @github: https://github.com/zy7y
    @gitee: https://gitee.com/zy7y
    @desc: 扩展方法, 2021/02/27
    关于exec执行python代码可查阅资料:https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p23_executing_code_with_local_side_effects.html
    
    """
    import time
    
    def exec_func(func: str) -> str:
        """执行函数(exec可以执行Python代码)
        :params func 字符的形式调用函数
        : return 返回的将是个str类型的结果
        """
        # 得到一个局部的变量字典,来修正exec函数中的变量,在其他函数内部使用不到的问题
        loc = locals()
        exec(f"result = {func}")
        return str(loc['result'])
    
    
    def get_current_highest():
        """获取当前时间戳"""
        return int(time.time())
    
    
    def sum_data(a, b):
        """计算函数"""
        return a + b
    
    

    tools/__init__.py

    写到这里发现篇幅过长了,所以这里就把改动的地方代码贴出来吧

    #!/usr/bin/env/python3
    # -*- coding:utf-8 -*-
    """
    @project: apiAutoTest
    @author: zy7y
    @file: __init__.py.py
    @ide: PyCharm
    @time: 2020/7/31
    """
    import json
    import re
    import allure
    from jsonpath import jsonpath
    from loguru import logger
    
    from tools.hooks import *
    
    
    ... 上方代码省略
    
    
    def rep_expr(content: str, data: dict, expr: str = '&(.*?)&') -> str:
        """从请求参数的字符串中,使用正则的方法找出合适的字符串内容并进行替换
        :param content: 原始的字符串内容
        :param data: 在该项目中一般为响应字典,从字典取值出来
        :param expr: 查找用的正则表达式
        return content: 替换表达式后的字符串
        """
        for ctt in re.findall(expr, content):
            content = content.replace(f'&{ctt}&', str(extractor(data, ctt)))
            logger.debug(f"&{ctt}& 替换的值为 {str(extractor(data, ctt))} ")
            
        # 增加自定义函数得的调用,函数写在tools/hooks.py中
        for func in re.findall('@(.*?)@', content):
            content = content.replace(f'@{func}@', exec_func(func))
            logger.debug(f"执行hooks函数{func} 替换的值为 {exec_func(func)}")
    
        return content
    
    ... 下方代码省略
    

    4. 源码地址

    github: https://www.github.com/zy7y/apiAutoTest

    gitee: https://www.gitee.com/zy7y/apiAutoTest

    其中最早版本(采用字典迭代方式处理依赖)在 version1.0分支

    5. 致谢

    感谢所有给予我帮助的人,文章,正在学习或使用apiAutoTest的同学们,其实个人开源这个项目以来个人有得到成就感,非常感谢

    参考资料:

    python3-cookbook

    6. 往日文章

    apiAutoTest: 开源啦

    apiAutoTest: 使用re库来处理接口之前的数据依赖

    apiAutoTest: 增加数据隔离(测试前后备份/还原数据库)

    apiAutoTest: 增加自定义函数,用例中可以调用

    作者:zy7y
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    自动化测试基础篇--Selenium文件上传send_keys
    自动化测试基础篇--Selenium中数据参数化之TXT
    自动化测试基础篇--Selenium中JS处理浏览器弹窗
    自动化测试基础篇--Selenium中JS处理滚动条
    自动化测试基础篇--Selenium单选框(Radio)复选框(CheckBox)
    自动化测试基础篇--Selenium弹出框alert
    自动化测试基础篇--Selenium select下拉框
    自动化测试基础篇--Selenium多窗口、句柄问题
    flask初探
    windows系统在python3.5环境下安装mysql组件
  • 原文地址:https://www.cnblogs.com/zy7y/p/14456202.html
Copyright © 2011-2022 走看看