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: 添加成功}

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()

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

动态变量

是为了这种场景:比如新增数据的接口,要求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')

断言封装

就是这里

在这里插入图片描述

这里通过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)

前面我写过做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}

执行测试

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

$ python mockserver.py

执行自动化测试

$ pytest testcases/testapi.py -vs

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BBJG_001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值