一.简介
pytest的自带功能很强大,通过添加插件可以扩展功能,pytest的代码结构适合定制和扩展插件,
可以借助hook函数来实现。
把fixture函数或者hook函数添加到conftest文件里,就已经创建了一个本地的conftest插件!
二.pytest plugin加载的几种方式:
1.内置plugins:从代码内部的_pytest目录加载;
2.外部插件(第三方插件):通过setuptools entry points机制发现的第三方插件模块;
推荐的第三方的pytest的插件:https://docs.pytest.org/en/latest/plugins.html
3.conftest.py形式的本地插件:测试目录下的自动模块发现机制
通过pytest --trace-config命令可以查看当前pytest中所有的plugin。
在pytest中,所谓plugin其实就是能被pytest发现的一些带有pytest hook方法的文件或对象。
三.What is a hook
要理解pytest hook,首先要知道什么是hook方法(钩子函数)。
这里举一个简单的例子,比如说你写了一个框架类的程序,然后你希望这个框架可以“被代码注入”,即别人可以加入代码对你这个框架进行定制化,该如何做比较好?一种很常见的方式就是约定一个规则,框架初始化时会收集满足这个规则的所有代码(文件),然后把这些代码加入到框架中来,在执行时一并执行即可。所有这一规则下可以被框架收集到的方法就是hook方法。
四.编写自己的插件
插件可以改变pytest行为,可用的hook函数很多,详细的定义:
http://doc.pytest.org/en/latest/_modules/_pytest/hookspec.html
1.pytest_addoption为例,基本每个pytest plugin都会有这个hook方法,它的作用是为pytest命令行添加自定义的参数。
parser:用户命令行参数与ini文件值的解析器
def pytest_addoption(parser):
parser.addoption("--env", ##注册一个命令行选项
default="test",
dest="env",
help="set test run env")
pytest_addoption: Hook function, 这里创建了一个argparser的group,通过addoption方法添加option,使得显示help信息时相关option显示在一个group下面,更加友好。
命令行输入:
pytest --help 就可以看到
2.修改pytest_collection_modifyitems
能解决什么实际问题?
测试case中 case名字为中文时,显示的时乱码!
完成所有测试项的收集后,pytest调用的钩子
def pytest_collection_modifyitems(items):
"""
测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上
所有的测试用例收集完毕后调用, 可以再次过滤或者对它们重新排序
items (收集的测试项目列表)
"""
for item in items:
item.name = item.name.encode("utf-8").decode("unicode_escape")
item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
3.可以实现自己的自定义动态参数化方案或扩展
def pytest_generate_tests(metafunc):
#""" generate (multiple) parametrized calls to a test function."""
if "param" in metafunc.fixturenames:
metafunc.parametrize("param",metafunc.module.par_to_test,ids=metafunc.module.case, scope="function")
然后测试用例的编写如下:
import pytest
import requests
from utils.get_data import get_data_path
from utils.get_data import get_test_data
import logging
case,par_to_test = get_test_data(get_data_path(__file__))
class TestFixture3(object):
"""
"""
def test_fixture_3(self,param,env):
url= env["host"]["local"]+env["APIS"]["add_message"]
response = requests.request("POST", url, data=param[3], headers=param[1])
res = response.json()
print(res)
测试数据:
{
"test": [
{
"case": "这是第一个测试用例",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"querystring": {
},
"payload": {
"mid" :"115",
"name" :"android9",
"content" : "8" ,
"status": "1",
"author" :"xixi"
},
"expected":
{
}
},
{
"case": "这是第2个测试用例",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"querystring": {
},
"payload": {
"mid" :"115",
"name" :"android9",
"content" : "8" ,
"status": "1",
"author" :"xixi"
},
"expected":
{
}
},
{
"case": "这是第3个测试用例",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"querystring": {
},
"payload": {
"mid" :"115",
"name" :"android9",
"content" : "8" ,
"status": "1",
"author" :"xixi"
},
"expected":
{
}
}
]
}
获取测试数据的代码:
def get_data_path(case_path):
file_name = os.path.dirname(case_path).split(os.sep + 'tests' + os.sep, 1)
test_data = os.sep.join([file_name[0], 'data', file_name[1], os.path.basename(case_path).replace('.py', '.json')])
return test_data
def get_test_data(test_data_path):
case = []
headers = []
querystring = []
payload = []
expected = []
with open(test_data_path,encoding='utf-8') as f:
dat = json.loads(f.read())
test = dat['test']
for td in test:
case.append(td['case'])
headers.append(td.get('headers', {}))
querystring.append(td.get('querystring', {}))
payload.append(td.get('payload', {}))
expected.append(td.get('expected', {}))
list_parameters = list(zip(case, headers, querystring, payload, expected))
return case,list_parameters
五.Conclusion
pytest通过这种plugin的方式,大大增强了这个测试框架的实用性,可以看到pytest本身的许多组件也是通过plugin的方式加载的,可以说pytest就是由许许多多个plugin组成的。另外,通过定义好一些hook spec,可以有效地控制plugin的“权限”,再通过类似pytest.hookimpl这样的装饰器又可以增强了各种plugin的“权限”。这种design对于pytest这样复杂的框架而言无疑是非常重要的,这可能也是pytest相比于其他测试框架中越来越?的原因吧。