封装思想
关键数据文件
配置数据:{project_name}.conf.yaml 存放配置信息,如环境、邮箱、数据库信息等
业务数据:{testcase_name}.api.yaml 存放接口信息,如:method、url、header、body等
用例数据:{testcase_name}.data.yaml 存放接口入参(测试用例)
过程数据:{testcase_name}.step.yaml 存放过程数据(借鉴httprunner),一般不用这个,可以忽略
封装思想
配置:根据配置⽂件获取初始配置和依赖
接口封装:接口调⽤进⾏抽象封装,类似PageObject效果
业务流程:业务⽤例设计,含有多个api形成的流程定义;不要再包含任何接口实现细节;断⾔
用例构建
使⽤package管理业务模块,使⽤class管理业务功能,使⽤method完成业务具体⾏为,使⽤配置⽂件读取初始配置,使⽤继承规划⽤例执⾏顺序,使⽤testcase完成测试⽤例的落地,使⽤assertion完成业务正确性校验,使⽤数据⽂件管理⽤例的数据驱动,使⽤jenkins完成持续集成。
具体实现
目录一览:
用例驱动
最简单,使用@pytest.mark.parametrize装饰器实现
步骤:
- 将用例写在yaml文件中
- 使用 yaml.safe_load读取文件
- 拿到读取的内容,使用@pytest.mark.parametrize传参
以管理企业标签为例:
# base_api.py 封装数据驱动框架,所有业务代码的父类
class BaseApi:
@classmethod
def yaml_load(cls, path):
# 封装yaml文件的加载
with open(path) as f:
return yaml.safe_load(f)
def jsonpath(self, path, r=None):
if r is None:
r = self.r.json() # 拿到上一次使用format方法中的r
return jsonpath(r, path)
# tag.py 业务代码
class Tag(BaseApi):
def create_tags(self, token, name):
url = 'https://qyapi.weixin.qq.com/cgi-bin/tags/create'
method = 'post'
params = {'access_token': token}
headers = {'Content-Type': 'application/json;charset=utf8'}
json = {'tag':{'name': name}}
res = request.request(method = method, url = url, params = params, headers = headers, json=json)
return res
def get_tags(self, token):
url = 'https://qyapi.weixin.qq.com/cgi-bin/tags/get'
method = 'get'
params = {'access_token': token}
res = request.request(method = method, url = url, params = params)
return self.api_send(self.data['get_tags'])
def delete_tags(self, token, id):
url = 'https://qyapi.weixin.qq.com/cgi-bin/tags/delete'
method = 'post'
params = {'access_token': token}
headers = {'Content-Type': 'application/json;charset=utf8'}
json = {'tag':{'id': id}}
res = request.request(method = method, url = url, params = params, headers = headers, json=json)
return res
# test_tag.py case文件
import pytest
from api.tag import Tag
from api.token import Token
class TestTag:
data = Tag.yaml_load("../data/test_tag.data.yaml")
token = Token().get_token('xxx')
t = Tag()
# @pytest.mark.parametrize('name', ['黄铜', '白银', '黄金'])
@pytest.mark.parametrize('name', data['test_delete'])
def test_delete(self, name):
# 删除标签用例
# 如果该标签已存在,就删除
self.t.get_tags(token)
x = self.tag.jsonpath(f'$..tag[?@.name=="{name}"]') # jsonpath,json的元素定位方式,类似于xml中的xpth,这里我们用的是自己封装后的jsonpath方法(在BaseApi中),此时不用传r,因为上一句中我们调用的get方法中使用了format方法,r便自动传入了我们封装的jsonpath方法中;所以只要我们调用了format方法,并保证这个r是适合的,就不用传r
if isinstance(x, list) and len(x) > 0:
self.t.delete_tags(token, tag_id=[x[0]['id']])
# 环境干净之后开始测试
self.t.get_tags(token)
path = '$..tag[?(@.name!="")]'
size = len(self.tag.jsonpath(path))
# 添加新标签并断言
self.t.create_tags(token, name)
self.t.get_tags(token)
assert len(self.tag.jsonpath(path)) == size + 1
# 删除标签
tag_id = self.tag.jsonpath(f'$..tag[?(@.name == "{name}")]')[0]['id']
self.t.delete_tags(token, tag_id)
# 获取标签并断言
self.t.get_tags(token)
assert len(self.tag.jsonpath(path)) == size
# test_tag.data.yaml yaml文件
"test_delete": ["demo1", "demo2", "demo3", "黄金"," ", "????" ]
业务驱动
将业务过程数据写在yaml方法中,包括请求方法,请求地址,请求参数,请求body,请求头等,在BaseApi中封装好通用的请求方法,业务类中直接调用,如下:
# BaseApi.py
class BaseApi:
yaml_params = {} # 定义好yaml_params,在业务方法中传入,以便yaml文件的内容替换
def api_send(self, req: dict): # req 传入加载好的yaml文件
req['params']['access_token'] = self.get_token(self.secret) # 给params的token赋值
# 模板内容替换 yaml文件中的变量赋值
raw = yaml.dump(req)
for key, value in self.yaml_params.items():
raw = raw.replace(f"${{{key}}}", repr(value))
req = yaml.safe_load(raw)
r = requests.request(
req['method'], # 传入yaml文件的method
url=req['url'], # 传入yaml文件的url
params=req['params'], # 传入yaml文件的params
json=req['json'] # 传入yaml文件的json
)
self.format(r)
return r.json()
# test_tag.api.yaml 业务过程的yaml文件
add:
method: post
url: https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag
params:
access_token: ${token}
json:
group_id: ${group_id}
tag:
- name: ${name}
# tag.py 业务文件
class Tag(Token):
def __init__(self):
self.data = self.api_load('../data/test_tag.step.yaml') # 把yaml文件中所有的api加载到当前类中
def get(self, group_id, name):
self.yaml_params['group_id'] = group_id # 调用时给BaseApi中的yaml_params传值,
self.yaml_params['name'] = name # 以便yaml文件的内容替换
res = self.api_send(self.data['add']) # 发请求
return res
过程驱动
涉及到多个接口关联的用例,如删除标签用例,要先获取标签,再添加标签,最后才删除标签,实际上我们调用了三个业务方法,中间还可能多次调用,如下:
这样写的话,用例中的代码太多了,这时可以将调用的方法和过程数据都封装在yaml文件中,在BaseApi中写好读取文件的代码,直接在用例中调用即可。主要理解getattr()方法的使用,执行当前调用对象的某一方法。
# base_api.py
def steps_run(self, steps: list):
for step in steps:
raw = yaml.dump(step)
for key, value in self.params.items():
raw = raw.replace(f"${{{key}}}", repr(value))
step = yaml.safe_load(raw)
if isinstance(step, dict):
if "method" in step.keys():
method = step['method'].split('.')[-1]
getattr(self, method)(**step)
if "extract" in step.keys():
self.data[step["extract"]] = getattr(self, 'jsonpath')(**step)
if "assertion" in step.keys():
assertion = step["assertion"]
if isinstance(assertion, str):
assert eval(assertion)
if assertion[1] == "eq":
assert assertion[0] == assertion[2]
yaml文件如下:
steps:
- {method: tag.get}
- {path: "$..tag[?(@.name==${name})]", extract: before}
- {method: tag.add, name: "${name}" }
- {method: get}
- {path: "$..tag[?(@.name==${name})]", extract: after}
- {assertion: [1, eq, 1]}
- {assertion: "len([1,2]) < len([1])" }