一、概述
fixture
是 pytest 框架提供的一个特性,用于管理测试中的资源和设定。它可以在测试函数或测试类中使用,以提供测试所需的固定数据、对象或执行特定的操作。
- 与 setup 和 teardown 相比,
fixture
提供了更灵活、可重用和可扩展的测试环境设置功能。 - 特点和用法:
- 灵活性 :
fixture
可以在测试函数、测试类或整个测试模块级别使用,以满足不同的测试需求。 - 可重用性 :
fixture
可以在多个测试函数或测试类中共享,并可以在不同的测试模块中重复使用。 - 参数化 :
fixture
可以接受参数,以便在测试时动态生成或配置测试数据和资源。 - 自动化 :pytest 会自动检测并调用适用的
fixture
,无需手动调用。
- 灵活性 :
二、参数列表
python
import pytest
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
pass
scope
:可以理解成fixture
的作用域,默认:function,还有 class、module、package、session 四个。( session 是整个测试会话,即开始执行 pytest 到结束测试。)autouse
(默认):False
,需要用例手动调用该fixture
;如果是True
,所有作用域内的测试用例都会自动调用该fixture
。name
(默认):装饰器的名称,同一模块的fixture
相互调用建议写个不同的 name。
三、通过 fixture 实现 setup 操作
3.1 三种调用方式
- 方式一:通过名称入参调用
python
import pytest
# 1.定义 fixture 。
@pytest.fixture
def login():
data = {}
print("\n登录完成!")
# 返回测试数据或对象给测试函数使用。
return data
def test_case_01():
print("无需登录的测试操作。")
# 2.通过 fixture 名称调用。
def test_case_02(login):
print("需要登录才能进行的测试操作。")
if __name__ == '__main__':
pytest.main(["-s"])
# [ 50%]无需登录的测试操作。
#
# 登录完成!
# [100%]需要登录才能进行的测试操作。
- 方式二:测试用例加上装饰器
@pytest.mark.usefixtures(fixture_name)
python
import pytest
@pytest.fixture
def login():
data = {}
print("\n登录完成!")
return data
# 1.再添加一个 fixture。
@pytest.fixture
def authentication():
print("\n认证完成!")
def test_case_01():
print("无需登录的测试操作。")
# 2.使用装饰器指定。
@pytest.mark.usefixtures("login", "authentication")
def test_case_02():
print("需要登录并通过验证,才能进行的测试操作。")
if __name__ == '__main__':
pytest.main(["-s"])
# [ 50%]无需登录的测试操作。
#
# 登录完成!
# 认证完成!
# [100%]需要登录并通过验证,才能进行的测试操作。
- 方式三:自动调用
python
import pytest
# 1.设置自动应用到每个测试函数中。
@pytest.fixture(autouse=True)
def query_database():
data = {}
print("\n完成数据库查询!")
return data
def test_case_01():
print("执行测试用例一。")
def test_case_02():
print("执行测试用例二。")
if __name__ == '__main__':
pytest.main(["-s"])
# 完成数据库查询!
# [ 50%]执行测试用例一。
#
# 完成数据库查询!
# [100%]执行测试用例二。
3.2 类声明调用
- 在类声明 上面加
@pytest.mark.usefixtures()
,代表这个类里面所有 测试用例都会调用该fixture
:
python
import pytest
@pytest.fixture
def query_database():
print("query_database")
# 在类上面声明。(所有用例都调用。)
@pytest.mark.usefixtures("query_database")
class Tests:
def test_case_01(self):
print("test_case_01")
def test_case_02(self):
print("test_case_02")
if __name__ == '__main__':
pytest.main(["-s"])
# query_database
# [ 50%]test_case_01
#
# query_database
# [100%]test_case_02
3.3 叠加使用
- 可以叠加多个
@pytest.mark.usefixtures()
,先执行的放底层,后执行的放上层:
python
import pytest
@pytest.fixture
def query_database():
print("query_database")
@pytest.fixture
def authentication():
print("authentication")
# 叠加使用。(从下到上执行。)
@pytest.mark.usefixtures("query_database")
@pytest.mark.usefixtures("authentication")
def test_case_01():
print("test_case_01")
if __name__ == '__main__':
pytest.main(["-s"])
# authentication
# query_database
# [100%]test_case_01
3.4 同时传多个参数
@pytest.mark.usefixtures()
可以传多个fixture
参数,先执行的放前面,后执行的放后面:
python
import pytest
@pytest.fixture
def query_database():
print("query_database")
@pytest.fixture
def authentication():
print("authentication")
# 传多个参数。(从前到后执行。)
@pytest.mark.usefixtures("authentication", "query_database")
def test_case_01():
print("test_case_01")
if __name__ == '__main__':
pytest.main(["-s"])
# authentication
# query_database
# [100%]test_case_01
3.5 获取 fixture 返回值
- 如果
fixture
有返回值,用@pytest.mark.usefixtures()
是无法获取到返回值的,必须用传参的方式:
python
import json
import pytest
@pytest.fixture
def query_database():
# mock 返回的数据。
data = {
"code": 0,
"msg": "SUCCESS",
"user": {
"name": "jan",
"age": 18
}
}
print("query_database successes!")
return json.dumps(data)
# 1.装饰器无法获取返回值。
@pytest.mark.usefixtures("query_database")
def test_case_01():
print("test_case_01")
# 2.通过名称入参才能获取返回值。
def test_case_02(query_database):
print("test_case_02 get res=", json.loads(query_database))
if __name__ == '__main__':
pytest.main(["-s"])
# test_case_01
# test_case_02 get res= {'code': 0, 'msg': 'SUCCESS', 'user': {'name': 'jan', 'age': 18}}
3.6 fixture 依赖其他 fixture
如果
fixture
还想依赖其他fixture
,需要用函数传参的方式,不能用@pytest.mark.usefixtures()
的方式,否则会不生效。
- 代码示例:
python
import pytest
@pytest.fixture(scope="session")
def open_browser():
print("===打开浏览器===")
# 1.依赖其他 fixture 。
@pytest.fixture
# 通过 @pytest.mark.usefixtures 调用不生效。
# @pytest.mark.usefixtures("open_browser")
# 需要通过名称入参。
def query_database(open_browser):
print("===查询数据库===")
def test_case_01(query_database):
pass
if __name__ == '__main__':
pytest.main(["-s"])
# ===打开浏览器===
# ===查询数据库===
四、实例化顺序
- 根据
scope
范围实例化: session > package > module > class > function - 具有相同作用域的
fixture
遵循测试函数中声明的顺序,并遵循fixture
之间的依赖关系。(在 fixture_A 里面依赖的 fixture_B ,则 fixture_B 优先实例化。) - 自动使用 (
autouse=True
)的fixture
将在显式使用(传参或装饰器)的fixture
之前实例化。
五、通过 fixture 实现 teardown 操作
5.1 yield 实现 teardown
fixture
需要搭配yield
关键字来开启 teardown 操作。
-
yield
关键字用于定义一个生成器函数。 -
生成器函数是一种特殊的函数,它不会像普通函数一样立即执行并返回一个结果,而是每次迭代 时返回一个值,并在下一个迭代时从上次离开的位置继续执行。
-
通过使用
yield
关键字,生成器函数可以保存其状态,这使得它们在处理大量数据时非常高效。 -
代码示例:
python
import pytest
@pytest.fixture(scope="session")
def open_browser():
print("===打开浏览器===")
yield
print("===关闭浏览器===")
@pytest.fixture
def query_database(open_browser):
print("===开始查询数据库===")
name = "jan"
age = 18
yield name, age
print("===数据库查询完成===")
def test_case_01(query_database):
print("===执行测试用例1===")
# 获取返回结果。
print("返回:", query_database)
name, age = query_database
# 断言。
assert "jan" == name
assert 18 == age
def test_case_02(query_database):
print("===执行测试用例2===")
if __name__ == '__main__':
pytest.main(["-s"])
# ===打开浏览器===
#
# ===开始查询数据库===
# PASSED ===执行测试用例1===
# 返回: ('jan', 18)
# ===数据库查询完成===
#
# ===开始查询数据库===
# PASSED ===执行测试用例2===
# ===数据库查询完成===
#
# ===关闭浏览器===
- 如果
yield
前面的代码,即 setup 部分已经抛出异常了,则不会执行yield
后面的 teardown 内容。 - 如果测试用例抛出异常,
yield
后面的 teardown 内容还是会正常执行。
5.2 yield + with 的结合
- 官方示例:
python
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
smtp_connection()
连接将测试完成执行后已经关闭,因为smtp_connection()
对象自动关闭 时,with
语句结束。
5.3 addfinalizer 终结函数
- 代码示例:
python
import pytest
@pytest.fixture(scope="module")
def open_browser(request):
print("===打开浏览器===")
def close_browser():
print("===关闭浏览器===")
# addfinalizer 将 close_browser 函数注册为终结器。
request.addfinalizer(close_browser)
def test_case_01(open_browser):
print("===执行测试用例1===")
def test_case_02(open_browser):
print("===执行测试用例2===")
if __name__ == '__main__':
pytest.main(["-s"])
# ===打开浏览器===
# PASSED ===执行测试用例1===
# PASSED ===执行测试用例2===
# ===关闭浏览器===
- 如果
request.addfinalizer()
前面的代码,即 setup 部分已经抛出异常了,则不会执行request.addfinalizer()
的 teardown 内容。 - 可以声明多个终结函数并调用。
六、结束语
"-------怕什么真理无穷,进一寸有一寸的欢喜。"
微信公众号搜索:饺子泡牛奶。