• pytest数据驱动


    完整代码

    有个人说过,自动化测试能让不懂代码的人只需要写测试case(数据)就能实现测试,我觉得他说的dei

    case使用封装

    数据case

    最终将只需要维护这个数据文件即可实现对100个接口的1000case(愿景)。

    # data/cases/case_api.yaml
    - name: friend_add
      url: http://127.0.0.1:5000//friend/add
      method: post
      request:
        json:
          uname: name_add_friend_${timestamp()}
          sex: 0
      validate:
        eq:
          - { kw: "$.status_code[0]", expected: 200 }
          - { kw: "$.code[0]", expected: 0 }
        in:
          - { kw: "$.message[0]", expected: 添加成功}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    yaml读html获取case参数

    # util/yaml_util.py
    def getCases(path):
      	with open(path) as f:
            yy = yaml.safe_load(f)
        return yy if yy else dict()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    testcase代码

    # testcases/testapi.py
    from utils.requests_util import RequestsUtil
    from utils.yaml_util import getCases
    from utils.assert_util import AssertUtil
    class TestApi:
    
        @pytest.mark.parametrize('case', getCases('data/cases/case_api.yaml'))	# 在这里读文件传测试数据
        def testApi(self, case):
            logging.info('=============== Start do case: {}'.format(case['name']))
            url = case['url']
            ret_json = RequestsUtil().request(case['method'], url, **case['request'])		# 调用封装的request
            AssertUtil().assert_resp(case['validate'], ret_json)												# 调用封装的assert
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    动态变量

    是为了这种场景:比如新增数据的接口,要求uname不能重复,怎么让它每天的参数传的不一样呢。用代码当然可以实现,这里就是实现了在case文件中(yaml)也能使用这些函数。我用了shell中取变量的语法来写,大概就是${python调用}这样。然后在执行case的时候,去把这中语法解析出来。这种类似反射的调用方式是不能被编译器的检查语法识别的。就是说在pycharm里,我即使没有写timestamp()的实现,它也不会包语法错误,但是执行的时候,就会报NameError: name 'xxxx' is not defined。就是说要保证你写的函数跟evalArgs()在同一个模块中,或者从其他地方导入进来。

    在这里插入图片描述

    函数写在了utils/__init__.py中,timestamp()的功能比较简单,就是拿到当前的时间戳。evalArgs通过正则来解析case中的${python调用},把它们替换成调用函数的结果。

    # utils/__init__.py
    def evalArgs(data):		# 解析参数中的动态参数/函数
        if type(data)==list:
            for i, di in enumerate(data):
                data[i] = evalArgs(di)
        if type(data)==dict:
            for k, v in data.items():
                data[k] = evalArgs(v)
        if type(data)==str:
            reg = re.compile(r'.*?(\$\{(.+?)\}.*?)+')		# 解析这样的格式 ${timestamp()}
            res = reg.findall(data)
            if res:
                for ri in res:
                    data = data.replace(ri[0], eval(ri[1]))		# 通过eval执行其中的函数
                    # 注意:要保证被eval执行的函数在当前作用域内存在(或者定义在当前文件,或者导入到当前文件)
            return data
        return data
    
    def timestamp():
        return time.strftime('%Y%m%d%H%M%S')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    断言封装

    就是这里

    在这里插入图片描述

    这里通过jsonpath的语法来定位返回结果中的元素

    # utils/assert_util.py
    import logging
    import jsonpath
    
    class AssertUtil:
        def assert_resp(self, validate, ret_json):
            for vtype, values in validate.items():
                for vi in values:
                    logging.info('Do assert, with msg: {}: {}'.format(vtype, vi))
                    # 通过jsonpath从响应结果中提取数据
                    if vi['kw'].endswith('[0]'):	# emm,非列表元素不能通过在keyword中的[0]提取
                        actual = jsonpath.jsonpath(ret_json, vi['kw'][:-3])[0]
                    else:
                        actual = jsonpath.jsonpath(ret_json, vi['kw'])
                    expected = vi['expected']
                    cmd = 'self.assert_{}(actual, expected)'.format(vtype)
                    try:
                        eval(cmd)
                    except NameError as e:
                        assert False, 'Exception in do assert, Not support assert type: {}, with msg: {}'.format(vtype, e)
    
        def assert_in(self, actual, expected):
            assert expected in actual, 'assert_in error, (expected){} not in (actual){}'.format(expected, actual)
            # 我这里是定义的 expected in actual,视个人需求可修改
    
        def assert_eq(self, actual, expected):
            assert actual == expected, 'assert_eq error, (expected){} != (actual){}'.format(expected, actual)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    前面我写过做requests的封装,这里为了能同时对response属性和后端返回的数据进行断言,我在封装requests的代码里把它们放在了一个字典中。

    # utils/requests_util.py
    ATTRS_RESP2RET = ['status_code', 'headers']
    
    	...
    
    	ret = {key: getattr(resp, key, '') for key in ATTRS_RESP2RET}
    
    	try:
      	ret_json = resp.json()
      except Exception as e:
        self.logger.error('Exception in dump resp to json, with resp.text: {}'.format(resp.text))
        raise e
        
      return {**ret, **ret_json}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行测试

    这里用flask简单写了个被测服务,启动该http服务

    $ python mockserver.py
    
    • 1

    执行自动化测试

    $ pytest testcases/testapi.py -vs
    
    • 1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGcv5RXc-1661695362014)(/Users/darcyzhang/Library/Application%20Support/typora-user-images/image-20220828215751857.png)]

  • 相关阅读:
    金九银十?铜九铁十才对......
    【java基础】有参构造和无参构造详解
    谈笑风声的秘密
    【Python Odyssey】1-1 | Python初见面
    基于DDTBOX,使用线性支持向量回归(SVR)从ERP数据中解码连续变量
    【数字图像处理】直方图均衡化与规定化
    npm包管理
    zookeeper核心源码分析
    conda常用指令
    【算法与数据结构】--算法和数据结构的进阶主题--算法的优化和性能调优
  • 原文地址:https://blog.csdn.net/BBJG_001/article/details/126575223