pytest 框架 +yaml 文件驱动 轻松接口自动化

卡特 测试交流1 162字数 2254阅读7分30秒阅读模式

去年开始做测试,一开始接触到的接口自动化测试 是 unittest+excel 的那种方式,我不喜欢 excel(纯个人喜好) 尤其是量大的时候 感觉用起来不是很顺手。后来接触到 pytest 便很喜欢,结合公司业务用 pytest+ATX 开发了 ui 自动化 以及用 selenium 开发了 web ui 自动化
前者没有应用起来,后者还是很稳定的,回头想想 还是想再做一下接口测试 毕竟接口还是性价比最高的,我现在分享的这个是基于一位前辈分享的基础上进行的改进。只是忘了那位前辈是谁了 真不好意思,此分享当是自己的一个学习记录吧

先看下 yaml 文件的设计 yaml 文件我也比较喜欢 可以用& * 等符合定义和引用变量 很方便 这样这个接口数据会比较少文章源自玩技e族-https://www.playezu.com/192561.html

---
Test:
desc: "登录"
parameters:
-
#测试登录
desc: 测试登录
method: post
url: /gw-driver/zhuanche-driver/driver/login
data:
-
request-ts: '1574940616636'
sign: 0841B69FB087643979BF464E34350AA3
type: '2'
··-
request-ts: '1574940616636'
sign: 0841B69FB087643979BF464E34350AA3
type: '1'
header: &header
car-pf: ANDROID_DRIVER
car-ps: shouqi
car-sv: 8.1.0
car-mv: OPPO OPPO PACM00
appCode: android_special_driver
assert_code: 200
assert_in_text: 登录成功
assert_contents:
code: 0

1:如何获取 yaml 文件中的数据 这个真的够啰嗦文章源自玩技e族-https://www.playezu.com/192561.html

class GetData():
def __init__(self,file_name):
'''
获取filename.yaml 配置文件的所有数据
'''
self.desc = []
self.method = []
self.url = []
self.data = []
self.header = []
self.assert_code = []
self.assert_text = []
self.assert_in_text = []
self.assert_body_has = []
log.info('解析yaml文件 path:' + str(path) + ' Param/Yaml/'+file_name+'.yaml')
param = get_param(file_name)
for i in range(0, len(param)):
self.method.append(param[i]['method'])
self.url.append(param[i]['url'])
self.header.append(param[i]['header'])
if 'desc' in param[i]:
self.desc.append(param[i]['desc'])
else:
self.desc.append(None)
if 'data' in param[i]:
self.data.append(param[i]['data'])
else:
self.data.append(None)
if 'assert_code' in param[i]:
self.assert_code.append(param[i]['assert_code'])
else:
self.assert_code.append(None)
if 'assert_text' in param[i]:
self.assert_text.append(param[i]['assert_text'])
else:
self.assert_text.append(None)
if 'assert_body_has' in param[i]:
self.assert_body_has.append(param[i]['assert_body_has'])
else:
self.assert_body_has.append(None)
# 断言数据列表化
if 'assert_in_text' in param[i]:
text = param[i]['assert_in_text']
Ltext = list(text.split(' '))
self.assert_in_text.append(Ltext)
else:
self.assert_in_text.append(None)

我以抛出问题的方式讲述吧
2:将接口数据写到 yaml 文件中 然后读取测试其实很简单 和 excel 操作一样,只需要封装一些操作 yaml 文件的函数即可.但是如果用 pytest 框架将如何实现多个接口驱动呢?
毕竟有很多接口其实都没有依赖性的 我们没有必要一个接口写一个 test 那样效率太堪忧 其实基于 pytest 的 parameterize 特性 一切就可以解决了文章源自玩技e族-https://www.playezu.com/192561.html

#urls 为读取到yaml文件中的所有url type:list 如何获取数据 查看 title1
@pytest.mark.parametrize('url',urls)
def test_home_page(self,url):
'''
:param url:
:return:
'''
request = Request.Request(res)
#获取测试数据在yaml文件中的索引 关于重复的url目前还没有解决 所以只支持不同的url
index=urls.index(url)
log.info('测试接口描述:'+str(descs[index])) #打印日志
response=request.send_request(methods[index],urls[index],params[index],headers[index])
assert test.assert_code(response['code'], assert_codes[index])
if assert_in_texts[index] is not None:
assert test.assert_in_text(response['body'],*assert_in_texts[index])
@pytest.mark.parametrize('url',base_conf.urls)
def test_home_page_config(self,get_init_data,url):
'''
测试首页单接口
:param get_init_data:
:param url:
:return:
'''
res=get_init_data
request = Request.Request(res)
#获取测试数据在yaml文件中的索引
index=base_conf.urls.index(url)
if base_conf.run[index] == False:
pytest.skip('skip')
base_conf.log.info('测试接口描述:'+str(base_conf.descs[index]))
replace_data(res,base_conf.headers[index],base_conf.params[index])
#处理一个接口有多个用例数据
if type(base_conf.params[index])==list:
for i in base_conf.params[index]:
response=request.send_request(base_conf.methods[index], base_conf.urls[index], i, base_conf.headers[index])
assert base_conf.test.assert_full(response,**{'assert_codes':base_conf.assert_codes[index],
'assert_in_texts':base_conf.assert_in_texts[index],'assert_content':base_conf.assert_content[index]})
else:
response=request.send_request(base_conf.methods[index],base_conf.urls[index],base_conf.params[index],base_conf.headers[index])
assert base_conf.test.assert_full(response,
**{'assert_codes': base_conf.assert_codes[index], 'assert_in_texts': base_conf.assert_in_texts[index],
'assert_content': base_conf.assert_content[index]})

3: 接上个问题 有关联的接口该怎么办 我还没有什么明智的解放 那就只能一个一个处理了,但是有关联的接口也可以再划分 比如登录接口,那是所以接口都会依赖的 主要是取其 token 数据 那就把它放到 conftest 中 登录一次 返回 token 即可文章源自玩技e族-https://www.playezu.com/192561.html

conftest.py
global res
@pytest.fixture(scope='session',autouse=True)
def login():
global res
res={}
#定义环境
env=Consts.API_ENVIRONMENT_DEBUG
index = descs.index(Consts.TEST_LOGIN)
res['env'] = env
res['token'] = ''
request = Request.Request(res)
log.info('---------------测试初始化--登录---------------')
response = request.send_request(methods[index], urls[index], params[index], headers[index])
log.info('接口返回结果  ' + str(response))
assert test.assert_code(response['code'], assert_codes[index])
assert test.assert_in_text(response['body'],*assert_in_text[index])
body=response['body']  #此处有封装成一个查找response的函数 工具函数最后分享
data=body['data']
tok=data['token']
res['env']=env
res['token']=tok
@pytest.fixture(scope='session')
def get_init_data():
'''
获取测试环境 以及司机token
:return: dict
'''
global res
return res

4:如何断言 前辈的 demo 中 有二种比对 比对全部 json 传 比对包含的数据 比对全部数据 使用情况极少,因为很少接口返回每次都一样的数据, 比对包含的字符我又用 我又加了一种 那就是比对 key-value,用到了 我上边说的工具函数 文章源自玩技e族-https://www.playezu.com/192561.html

在 yaml 文件中写法文章源自玩技e族-https://www.playezu.com/192561.html

assert_code: 200
assert_in_text: 登录成功
assert_contents:
code: 0
data: 1

断言代码文章源自玩技e族-https://www.playezu.com/192561.html

def assert_in_text(self,body,*expect_data):
'''
验证 body中是否包含预期字符串
:param body:
:param expect_data:
:return:
'''
#text = json.dumps(body, ensure_ascii=False)
text=str(body)
try:
for i in expect_data:
assert i in text
return True
except:
self.log.error("Response body != expected_msg, expected_msg is %s, body is %s" % (expect_data, body))
raise
def assert_content(self,body,**expectes_msg):
try:
for key,value in expectes_msg.items():
res=get_target_value(key,body,[])
assert res[0]==str(value)
return True
except:
self.log.error("Response body != expected_msg, expected_msg is %s, body is %s" % (expectes_msg, body))
raise

工具函数文章源自玩技e族-https://www.playezu.com/192561.html

def get_target_value(key, dic, tmp_list):
"""
:param key: 目标key值
:param dic: JSON数据
:param tmp_list: 用于存储获取的数据
:return: list
"""
if type(dic)!=dict or type(tmp_list)!=list:
return 'argv[1] not an dict or argv[-1] not an list '
if key in dic.keys():
tmp_list.append(dic[key])  # 传入数据存在则存入tmp_list
else:
for value in dic.values():  # 传入数据不符合则对其value值进行遍历
print(type(value))
if type(value)==dict:
get_target_value(key, value, tmp_list)  # 传入数据的value值是字典,则直接调用自身
elif isinstance(value, (list, tuple)):
_get_value(key, value, tmp_list)  # 传入数据的value值是列表或者元组,则调用_get_value
return tmp_list

5:测试中总不可能只涉及到一个被测系统 比如需要修改一些配置等 那肯定要设计到后台的一些功能 当然用 selenium 的方式也可以实现 这里还是统一风格 用接口吧
现在公司内网所有的平台都是用 sso 登录的 所以就遇到怎么用 sso 实现登录文章源自玩技e族-https://www.playezu.com/192561.html

def login():
'''
登录
:return:
'''
# 获取接口在yaml文件中的索引
index = descs_admin.index(Consts.ADMIN_LOGIN)
api_url = req_url_admin + urls_admin[index]
log.info('admin登录')
log.info(api_url)
'''登录/a'''
session=HTMLSession()
response=session.get(api_url,headers=headers_admin[index])
#获取sso重定向地址
api_url=response.url
It=response.html.xpath('//input[@name="lt"]/@value')[0]
execution=response.html.xpath('//input[@name="execution"]/@value')[0]
print(It)
params_admin[index]['lt']=It
params_admin[index]['execution'] = execution
headers_admin[index]['Referer'] = api_url
'''登录sso'''
res=session.post(api_url,params_admin[index],headers_admin[index])
assert res.status_code==200
return session
@pytest.fixture()
def test():
'''
:return:
'''
request=admin_login()
# 获取接口在yaml文件中的索引
index = descs_admin.index(Consts.Test)
api_url = req_url_admin + urls_admin[index]
if methods[index] == 'post':
response = request.post(api_url, params_admin[index], headers_admin[index])
print(response)
log.info(str(response))
assert test.assert_code(response.status_code, assert_codes_admin[index])

6: pytest-html 默认的报告模式无法满足我 ,我只想保留错误接口的日志输出 ,想要添加一些 description 来说明接口测试的流程 所以需要在最外层的 conftest.py 中添加两个 hook文章源自玩技e族-https://www.playezu.com/192561.html

#使用日志为空的通知替换所有附加的HTML和日志输出
@pytest.mark.optionalhook
def pytest_html_results_table_html(report, data):
if report.passed:
del data[:]
@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
cells.insert(2, html.th('Description'))
cells.insert(3, html.th('Time', class_='sortable time', col='time'))
cells.pop()

7:用例不通过 如何快速定位 当日是通过详细的 log 了 我的 log 信息 记录了每一个接口的输入输出 以及对用例步骤进行了日志的区分 如下
虽然很简单 但是可以让你的日志看起来相当舒服 整个流程也一目了然

@pytest.fixture(scope='module',autouse=True)
def log_init():
log.info('---------------TestAfterMarket-TestRefundAfArrive CASE1 测试功能1 START-----------')
yield
log.info('---------------TestAfterMarket-TestRefundAfArrive CASE1 测试功能2 END-----------')

软件测试功能测试方法

 
    • 向右向左转
      向右向左转 9

      展示的代码总,部分内容好像在哪里看岛国,再说语法和风格还可以再提炼提炼。该死的输入法,打字太快了,更正下:展示的代码中,部分内容好像在哪里看到过,再说语法和风格还有可以提炼提炼的地方。看过对啊 我说过我是基于前辈的做的改进啦不错,pytest 比 unittest 灵活,Excel 存放 case,操作太麻烦了同感get_param 这个函数里面是啥返回单个 yaml 文件 的所有 parameters 信息可以给下 github 项目地址吗,想学习下 web ui 框架,有 git 可以分享吗怎么能让普通函数可以得到 fixture 的返回结果呢?可以用全局变量 或者写入文件的方式最后我用了很笨的办法解决了,但是我觉得肯定有很好的办法解决,但是等不了了。谢谢你的分享。请问楼主,get_param()实现与 with open (“”, encoding=”utf-8″) as f: 有什么区别吗get_param 是封装了对 yaml 文件的读取和拆分,当中是有用到 python 的 open 方法的哈
      仅楼主可见pytest+yaml 设计接口自动化框架过程记录 https://gitee.com/your_dad_died/pytest_api_yaml

    匿名

    发表评论

    匿名网友
    确定

    拖动滑块以完成验证