pytest使用(3)-conftest
接着上一节的fixture,这一节继续介绍conftest.我们知道在python中要想实现数据的共享,可以定义一个“全局变量”,在另一个文件中使用的时候通过先导入,再使用的方式来访问。pytest中定义个conftest.py来实现数据,参数,方法、函数的共享。conftest.py 的文件名称是固定的, pytest 会自动识别该文件,我们可以理解成一个专门存放 fixture 的配置文件。一
接着上一节的fixture,这一节继续介绍conftest.
我们知道在python中要想实现数据的共享,可以定义一个“全局变量”,在另一个文件中使用的时候通过先导入,再使用的方式来访问。pytest中定义个conftest.py来实现数据,参数,方法、函数的共享。
conftest.py 的文件名称是固定的, pytest 会自动识别该文件,我们可以理解成一个专门存放 fixture 的配置文件。一个工程下可以建多个 conftest.py 文件,一般我们都是在工程根目录下设置的 conftest 文件,这样会起到一个全局的作用。 我们也可以在不同的子目录下放 conftest.py ,这样作用范围只能在该层级的子目录下生效。
总而言之:conftest.py文件是Pytest特有配置文件,只能用来做如下三个功能:
- 设置项目和fixture
- 导入外部插件
- 指定钩子函数
conftest特点
1、conftest.py可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture
2、conftest.py与运行的用例要在同一个pakage下,并且有init.py文件
3、不需要import导入 conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了,如果放到某个package下,那就在改package内有效,可有多个conftest.py
4、conftest.py配置脚本名称是固定的,不能改名称
5、conftest.py文件不能被其他文件导入
6、所有同目录测试文件运行前都会执行conftest.py文件
在上一章节中结识了fixture。fixture里面有个scope参数可以控制fixture的作用范围:session>module>class>function
function:每一个函数或方法都会调用
class:每一个类调用一次,一个类中可以有多个方法
module:每一个.py文件调用一次,该文件内又有多个function和class
session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
conftest结合fixture使用
scope参数为session:所有测试.py文件执行前执行一次
scope参数为module:每一个测试.py文件执行前都会执行一次conftest文件中的fixture
scope参数为class:每一个测试文件中的测试类执行前都会执行一次conftest文件中的
scope参数为function:所有文件的测试用例执行前都会执行一次conftest文件中的fixture
conftest使用场景
fixture适用于在同一个py文件中多个用例执行时的使用;而conftest.py方式适用于多个py文件之间的数据共享。比如常见的有以下场景:
- 请求接口需要共享登录接口的token/session
- 多个case共享一套测试数据
- 多个case共享配置信息
conftest示例
# conftest.py
import pytest
@pytest.fixture(scope='session')
def get_token():
token = 'qeehfjejwjwjej11sss@22'
print('conftest中輸出token:%s' % token)
return token
# test_02.py
import pytest
class Test(object):
def test2(self, get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test02.py-Test类-test2用例,获取get_token:%s】" % get_token)
assert get_token == token
if __name__ == "__main__":
pytest.main(["-s", "test_02.py", "test_03.py"])
# test_03.py
import pytest
class Test(object):
def test3(self, get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test03.py-Test类-test3用例,获取get_token:%s】" % get_token)
assert get_token == token
def test4(self, get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test03.py-Test类-test4用例,获取get_token:%s】" % get_token)
assert get_token == token
文件目录层级如下所示
打开testreports.html
当conftest.py中的fixture(scope=session)时,所有的测试py文件执行前执行一次
当conftest.py中的fixture(scope=module)时,每一个测试.py文件执行前都会执行一次conftest文件中的fixture
当conftest.py中的fixture(scope=class)时,每一个测试文件中的测试类执行前都会执行一次conftest文件中的
当conftest.py中的fixture(scope=function)时,所有文件的测试用例执行前都会执行一次conftest文件中的fixture
yield实现teardown
每个测试用例完成后,应该做好资源回收,此时就需要使用到 teardown函数的善后工作了。用 fixture 实现 teardown 并不是一个独立的函数,而是用 yield 关键字来开启 teardown 操作。
当 pytest.fixture(scope=“session”) 时,作用域是整个测试会话,即开始执行pytest 到结束测试只会执行一次。
当 pytest.fixture(scope=“module”) 时, module 作用是整个 .py 文件都会生效(整个文件只会执行一次),用例调用时,参数写上函数名称就可以。
当 pytest.fixture(scope=“class”) 时,每一个测试文件中的测试类执行前都会执行一次conftest文件中的
当 pytest.fixture(scope=“function”) 时,pytest 的 yield 类似 unittest 的 teardown 。每个方法(函数)都会执行一次。
修改conftest.py文件
# conftest.py
import pytest
@pytest.fixture(scope='session')
def get_token():
token = 'qeehfjejwjwjej11sss@22'
print('conftest中开始输出token:%s' % token)
yield token
print('conftest中结束输出token:%s' % token)
test02.py修改如下所示:
# test_02.py
import pytest
class Test(object):
def test2(self, get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test02.py-Test类-test2用例,获取get_token:%s】" % get_token)
assert get_token == token
class Test01(object):
def test_01(self, get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test02.py-Test类-test2用例,获取get_token:%s】" % get_token)
assert get_token == token
test03.py文件不变。执行输出命令
scope=session时
scope=module时,每个文件开始调用conftest初始化,py执行完成后,调用teardown回收
scope=class时,每个类开始时调用conftest初始化,类中的用例执行完成后,调用teardown完成数据回收
scope=function时
addfinalizer实现回收
除了 yield 可以实现 teardown ,在 request-context 对象中注册 addfinalizer 方法也可以实现终结函数。在用法上, addfinalizer 跟 yield 是不同的,需要你去注册作为终结器使用的函数。例如:增加一个函数 myteardown*,并且注册成终结函数。
# conftest.py
import pytest
@pytest.fixture(scope="session")
def get_token(request):
token = 'qeehfjejwjwjej11sss@22'
print('conftest中开始输出token:%s' % token)
# yield token
def myteardown1():
print('conftest中结束1输出token:%s' % token)
def myteardown2():
print('conftest中结束2输出token:%s' % token)
request.addfinalizer(myteardown1)
request.addfinalizer(myteardown2)
return token
参数传递
参数传递有两个方向,一个是case给conftest.py传递参数,另一个是case中pytest.mark.parametrize给用例传递参数,下面介绍一下常用的参数传递方式。
parametrize向下给case传递参数
@pytest.mark.parametrize('policy', ['REJECT', 'CONTINUE'])
def test_create_func(policy):
res, code = obj.openapi_create_func(ploicy=ploicy,
id=***,
)
这种是向下传递参数,该case会执行两个场景,分别是policy中的两个参数
parametrize向上给conftest传递参数
对于有些场景,需要将部分内容提炼到conftest.py中。同时指定conftest中fixture函数的scope范围已经是否autouse。如弹性编排的自动化测试用例,对于弹性伸缩下的其他资源来说,伸缩组和伸缩配置相关信息可以提取放在conftest.py中,因为这个是弹性编排的基础部分。项目结构如下
为方便介绍,后文中第一个conftest称之为conftest1,类似第二个conftest称之为conftest2。
#conftest1底层的结构
import pytest
@pytest.fixture()
def preparefunc(request, as_client, *args):
cooldown = desirenum = minnum = maxnum = 0
if hasattr(request, 'param'):
cooldown = request.param.get('cooldown')
desirenum = request.param.get('desirenum')
minnum = request.param.get('minnum')
maxnum = request.param.get('maxnum')
result, status_code = as_client.open_api_create_func(cooldown=cooldown,
desirenum=desirenum,
minnum=minnum,
maxnum=maxnum)
assert status_code == 200
return result
def teardown():
...
request.addfinalizer(teardown)
@pytest.fixture()
def vm(request, as_client, *args):
...
如果prepareas,在用例不传递参数则使用默认参数,如果传递参数则使用传递的参数给conftest中的fixture函数。在用例层
#test_scaleout_csas.py
import allure
import pytest
class TestLifycycleHook():
@allure.title(f'case使用conftest中的默认参数')
def test_scaleout_policy001(self, prepareas):
...
@allure.title(f'parametrize给conftest中的一个fixture函数传递参数并将conftest返回值作为case的参数进行使用')
@pytest.mark.parametrize('prepareas', [{'desirenum': "1",
'minnum': '2'}], indirect=True)
def test_scaleout_policy002(self, prepareas):
...
@allure.title(f'parametrize给conftest中的两个个fixture函数传递参数并将conftest返回值作为case的参数进行使用')
@pytest.mark.parametrize('prepareas, vm', [{'count': '1', 'charType': 'prepaid'},
{'desirenum': "1", 'minnum': '2'}],
indirect=True)
def test_scaleout_policy003(self, prepareas):
...
在调用vm的创建ECS虚拟机的时候,有时需要同时指定系统盘和数据盘。但是创建云盘的时候,系统盘和数据盘的key是一样的,此时如果传递的是如下格式
@pytest.mark.parametrize('vm', [{'kind':'system'}, {'kind':'data'}], indirect=True)
def test_vm(vm):
...
则被pytest框架认为是创建两个ECS虚拟机,而不是一个云盘类型同时包括系统盘和数据盘
parametrize向上给conftest传递相同key的多个数据
此时可以使用namedtuple将如上数据封装成一个对象。当然也可以直接将两个数据使用tuple封装后然后在conftest中的vm解封装也可以。这里介绍下使用namedtuple
# test_create_vm
import pytest
from collections import namedtuple
func = namedtuple('VM', ['name', 'system', 'data'])
data = func('functest', {'kind':'system'}, {'kind':'data'})
@pytest.mark.parametrize('vm', [vmdata,], indirect=Ture)
def test_case(vm):
...
# conftest.py
@pytest.fixture()
def vm(request):
...
接着上文,返回到项目工程目录,在lifecyclehooktest下还有一个conftest用于封装和life*相关的内容。这里conftest2如下:
#conftest2
import pytest
@pytest.fixture()
def createfunc(request, as_client, prepareas):
res, code = as_client.open_api_create_func(id=prepareas, name='zhiyu')
assert code == 200
return res
因为conftest2中的创建依赖conftest1中的 prepareas。在测试lifechclye中希望给prepareas传递参数后将其返回值用于conftest2创建createlifecyclehook,并将其返回给测试用例中来。这个时候需要使用到lazy_fixture。pytest-lazy-fixture 插件,解决在测试用例中使用 @pytest.mark.parametrize 参数化时调用 fixture。
pytest-lazy-fixture
需求是如下:test_scaleout_case中使用到了创建hooks,因此使用到conftest2,conftest2需要指定伸缩组中desireNumber的数量并使用scalinggroup,因此使用到conftest1。
参考:pytest-lazy-fixture
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)