fixture
0、文档
pytest-fixture
fixture
pytest-fixture
1、fixture简介
@pytest.fixture() 装饰器用于声明函数是一个fixture,该fixture的名字默认为函数名,也可以自己指定名称(通过name参数可指定别名);如果测试用例的参数列表中包含fixture的名字,那么pytest会根据名字检测到该fixture,并在测试函数运行之前执行该fixture。
- 测试函数可以直接使用fixture名称作为输入参数,在这种情况下,fixture函数返回的fixture实例将被注入。
- 可以使用yield或者return关键字把fixture函数里的值传递给test函数。
- 测试函数可以使用多个fixture;
- fixture本身还可以使用其他的fixture;
1.1 fixture参数
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
参数:
- scope – 定义被 @pytest.fixture修饰的方法的作用域;有 “function” (默认) “class” , “module” , “package” 或 “session” 。
session:每次会话只需要运行一次,会话内所有模块、类、方法,都共享这个fixture
module:每一个.py文件调用一次
class:每一个类中调用一次
function:每一个function或者类方法中都会调用
动态作用域:
在某些情况下,您可能希望更改fixture的作用域而不更改代码。为此,将一个可调用对象传递给scope。该可调用对象必须返回一个具有有效作用域的字符串,并且只会执行一次——在fixture定义期间。它将使用两个关键字参数调用——fixture_name作为字符串,config使用配置对象。
这在处理需要时间安装的fixture时特别有用,比如生成一个docker容器。您可以使用命令行参数来控制派生容器在不同环境下的作用域。
def determine_scope(fixture_name, config):
if config.getoption("--keep-containers", None):
return "session"
return "function"
@pytest.fixture(scope=determine_scope)
def docker_container():
yield spawn_container()
- params – 参数化实现数据驱动(可支持列表、元组、字典列表、字典元组),获取当前参数可使用
request.param
。
①Fixture可选的参数列表,支持列表传入
②默认为None,每个param的值。
③可通过request.param接受设置的返回值,params中有多少个元素,在测试时,引用次fixture的函数就会调用几次。
④可与参数ids一起使用,作为每个参数的标识。
# -*- coding: utf-8 -*-
import pytest
@pytest.fixture(scope="function", params=["hello", "hi", 123])
def ddt(request): # 通过request接收params中的参数
print("=====setup======")
yield request.param # 通过request.param获取当前使用的参数
print("=====teardown======")
class TestDemo:
def test_one(self):
print("---------hello world--------")
# 因autouse为False,故需显示引用,即测试函数可以直接使用fixture名称作为输入参数
def test_two(self, ddt):
print(f"--------{ddt}-------")
if __name__ == '__main__':
pytest.main("-vs")
- autouse --如果为True,则
自动执行
fixture,无需在测试函数使用fixture名称作为输入参数。如果为False(默认值),则需要显式引用(测试函数可以直接使用fixture名称作为输入参数)。 - ids – 当有多个params时,针对每一个param,可以指定id,这个id将变成测试用例名字的一部分。如果没有提供id,id将自动生成。
- name – 默认是fixture函数的名称,可以通过name参数更改这个fixture的名称。更改后,如果这个fixture被调用,则使用更改后的名称。
1.2 fixture调用
1)参数传参:
- 将fixture函数名当参数传入用例(函数名无引号)
- 支持多个,支持fixture相互调用时给fixture传参
- 返回值:fixture执行完毕将返回值赋值给用例参数名,无返回值默认为None
2)装饰器传参:
- 支持多个,不支持fixture相互调用时给fixture传参
- 返回值:不能获取
- 第一种:传入名字,@pytest.mark.usefixtures(“fixture1”, “fixture2”)
(字符串格式,带引号的) - 第二种:多个可以使用@pytest.mark.usefixture()进行叠加,先执行的放底层,后执行的放上层。
fixture可相互调用,但要注意:如果级别不同,低级别可以调用高级别,高级别不能调用低级别。
1.3 fixture的实例化顺序
影响fixture实例化顺序的三个因素是:
- 作用域(scope)
- 依赖项
- 自动调用(autouse)
import pytest
@pytest.fixture(scope="session")
def order():
return []
@pytest.fixture
def func(order):
order.append("function")
@pytest.fixture(scope="class")
def cls(order):
order.append("class")
@pytest.fixture(scope="module")
def mod(order):
order.append("module")
@pytest.fixture(scope="package")
def pack(order):
order.append("package")
@pytest.fixture(scope="session")
def sess(order):
order.append("session")
class TestClass:
def test_order(self, func, cls, mod, pack, sess, order):
assert order == ["session", "package", "module", "class", "function"]
六个fixture函数:
order:scope为session级别,返回一个空list。
func: 调用了order,scope为默认的function级别,并实现向order返回的列表中插入”function“的操作。
cls:调用了order,scope为class级别,并实现向order返回的列表中插入”class“的操作。
mod:调用了order,scope为module级别,并实现向order返回的列表中插入”module“的操作。
pack:调用了order,scope为package级别,并实现向order返回的列表中插入”package“的操作。
sess:调用了order,scope为session级别,并实现向order返回的列表中插入”session“的操作。
结论:
1.fixture的scope级别越高,那么它执行的优先级越高。优先级为:session>package>module>class>function
2.fixture如果存在依赖项,那么它所依赖的fixture函数会先被执行。
3.同scope级别fixture中,自动使用的fixture会最先执行;若该fixture存在依赖项,则对于调用了fixture的测试函数来说,这些依赖项也可以看做是自动使用的。
2、局部前置处理
fixture写在测试文件中:
import pytest
@pytest.fixture()
def fix_add(request):
print("fixture拿到的原始参数是:", request.param)
sum = request.param[0] + request.param[1]
yield sum
@pytest.mark.parametrize("fix_add", [(1, 3), (2, 4)], indirect=True)
def test_add(fix_add):
print('-----执行fix_add测试用例------')
print(f"fixture返回的参数和是:{fix_add}")
3、全局前置处理
一个工程下可以有多个conftest.py的文件,在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在该层级以及以下目录生效,另conftest是不能跨模块调用的。在conftest.py定义的fixture不需要进行import,pytest会自动查找使用。
# -*- coding: utf-8 -*-
import pytest
from logic.gen.account.manager import AccountManager
from util.context.context import Context
def pytest_addoption(parser):
# 自定义命令行参数
parser.addoption('--passenger', action='store', help='passenger account')
parser.addoption('--driver', action='store', help='driver account')
@pytest.fixture
def replace_accounts(request):
# 获取命令行参数的值
passenger = request.config.getoption('--passenger')
driver = request.config.getoption('--driver')
print(f"----------{passenger}---{driver}----------")
return (
AccountManager(ctx=Context(), account=passenger),
AccountManager(ctx=Context(), account=driver))
测试文件:
class TestDemo:
# 因autouse为False,故需显示引用,即测试函数可以直接使用fixture名称作为输入参数
def test_one(self, replace_accounts):
p, d = replace_accounts[0], replace_accounts[1]
print(f"==========={type(p)}===========")
print(f"==========={d}===========")
执行:python3 -m pytest -vs --passenger HXZPassenger --driver HXZDriver test_666.py
4、实战–用例执行时替换账号(待改进???)
import importlib
import pytest
@pytest.fixture
def replace_accounts(request):
case_name = request.param.get('case_name')
passenger_phone = request.param.get('passenger_phone')
driver_phone = request.param.get('driver_phone')
module_name = "tests.test_{}".format(case_name)
test_module = importlib.import_module(module_name) # 通过importlib模块中的import_module函数动态导入指定测试用例模块
setattr(test_module, "passenger_phone", passenger_phone) # 用setattr函数来动态修改测试用例中的参数值
setattr(test_module, "driver_phone", driver_phone)
return test_module.test_case # fixture返回修改后的测试用例函数