pytest 是一个非常灵活且强大的测试框架,它支持简单的单元测试到复杂的功能测试。显著特点是其简洁的语法,可以无需继承 TestCase 类直接使用函数来编写测试用例,并通过 assert语句 进行断言。还支持参数化测试、插件系统以及丰富的 assertion rewriting 功能,使编写测试更加便捷和直观。我们可以使用它进行自动化测试。
pytest 的 Fixture 系统使我们能够定义一个通用的设置步骤,该步骤可以反复使用,就像使用普通函数一样。两个不同的测试可以请求同一个 Fixture,然后 pytest 会根据该 Fixture 为每个测试测试用例提供各自的结果。
这对于确保测试不会相互影响非常有用。我们可以使用此系统确保每个测试都获得自己的新数据,并从干净的状态开始,以便提供一致、可重复的结果。
PS: 本文基于pytest 8.3.3,建议使用PyCharm编写并运行测试套件,方便。
简单使用 @pytest.fixture 装饰器
可以在 Package/ 下创建 fixture_fun.py ,定义 Fixtures :
Project/
│
├── Config/
│ └── pytest.ini # 自定义 mark 标记
│
├── Data/
│ └── parametrize.json # 根据策略模块
│
├── Package/ # 程序目录
│ ├── __init__.py # 包初始化文件,可以定义一些变量或执行一些操作。当然里面什么都不写也可以。
│ ├── fixture_fun.py # 定义Fixture,比如初始化数据、清理数据等操作
│ ├── module1.py # 应用程序模块,比如连接数据库操作数据,接口请求等操作,推荐按功能封装成类
│ └── module2.py # 应用程序模块,比如连接数据库操作数据,接口请求等操作,推荐按功能封装成类
│
├── Test/ # 测试用例目录
│ ├── __init__.py # 包初始化文件
│ ├── test_module1.py # 测试 module1 的测试用例
│ └── test_module2.py # 测试 module2 的测试用例
├── Tools/ # 工具目录
│ ├── __init__.py # 包初始化文件
│ └── files_processor.py # 文件处理工具
├── app.py # 项目启动文件
├── requirements.txt # 项目依赖项列表
└── README.md # 项目说明文档
被 @pytest.fixture 装饰的函数可以作为测试用例请求,也就是钩子函数:
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
# 返回 'x'
@pytest.fixture
def str_function():
return "X"
# 返回 test 函数
@pytest.fixture
def function_function():
def test(str_test):
return str_test
return test
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import str_function, function_function
# 此时 str_function 是 "X"
def test_1(str_function):
assert "X" == str_function
# 此时 function_function 是 test 函数
def test_2(function_function):
assert "JZY" == function_function("JZY")
assert "JZ" == function_function("JZ")
assert "JCY" == function_function("JCY")
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 2 items
Config\test_module1.py::test_1 PASSED [ 50%]
Config\test_module1.py::test_2 PASSED [100%]
================================================= 2 passed in 0.01s ==================================================
灵活使用 Fixtures
多个测试用例请求同一个 Fixture:
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
# 返回 "Hello Word!"
@pytest.fixture
def test():
return "Hello Word!"
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import test
def test_1(test):
assert 11 == len(test)
def test_2(test):
assert 'H' in test
def test_3(test):
assert "Hello Word!" == test
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 3 items
Config\test_module1.py::test_1 PASSED [ 33%]
Config\test_module1.py::test_2 PASSED [ 66%]
Config\test_module1.py::test_3 PASSED [100%]
================================================= 3 passed in 0.01s ==================================================
单测试用例请求多个 Fixture :
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
@pytest.fixture
def one():
return "J"
@pytest.fixture
def two():
return "Z"
@pytest.fixture
def three():
return "Y"
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import one, two, three
def test_test(one, two, three):
assert "JZY" == one+two+three
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 1 items
Config\test_module1.py::test_test PASSED [100%]
================================================= 1 passed in 0.01s ==================================================
Fixtures 请求其它 Fixtures:
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
@pytest.fixture
def before():
return "JZY"
@pytest.fixture
def after(before):
return before
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import after, before
def test_test(after):
assert "JZY" == after
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 1 items
Config\test_module1.py::test_test PASSED [100%]
================================================= 1 passed in 0.01s ==================================================
自动请求 Fixtures :
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
database = []
@pytest.fixture(autouse=True) # autouse=True 时,pytest 会自动执行
def database_insert():
database.append('JZY')
return True
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
# database_insert 被引用就会执行
from Package.fixture_fun import database_insert, database
def test_test():
assert database == ['JZY']
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 1 items
Config\test_module1.py::test_test PASSED [100%]
================================================= 1 passed in 0.01s ==================================================
通过 @pytest.mark.usefixtures 请求 Fixtures :
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
database = []
@pytest.fixture()
def database_insert():
database.append('JZY')
return True
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import database_insert, database
@pytest.mark.usefixtures("database_insert")
def test_test():
assert database == ['JZY']
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 1 items
Config\test_module1.py::test_test PASSED [100%]
================================================= 1 passed in 0.01s ==================================================
注意: 使用 @pytest.mark.usefixtures 请求 Fixtures ,测试用例不会获取到 Fixtures 的返回值。
Fixtures 作用域
Fixtures 可以设置为 5 个不同的作用域,分别是:
- session:整个测试会话(即所有模块)只会初始化一次 Fixtures 。
- package:包(即目录)中只会初始化一次 Fixtures 。
- module:模块中只会初始化一次 Fixtures 。
- class:测试组中只会初始化一次 Fixtures 。
- function:每个测试用例都会初始化一次 Fixtures 。
设置 Fixtures 作用域为 function :
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
# 定义一个作用域为 function 的 fixture
@pytest.fixture(scope="function")
def my_list():
return [1, 2, 3]
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import my_list
def test_one(my_list):
my_list.append(4)
assert my_list == [1, 2, 3, 4]
def test_two(my_list):
assert my_list == [1, 2, 3]
def test_three(my_list):
assert my_list == [1, 2, 3]
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 3 items
Config\test_module1.py::test_one PASSED [ 33%]
Config\test_module1.py::test_two PASSED [ 66%]
Config\test_module1.py::test_three PASSED [100%]
================================================= 3 passed in 0.01s ==================================================
每一个测试用例请求 Fixtures:my_list ,都会初始化 my_list 的值,每个测试用例接收的值都是 [1, 2, 3] 。
设置 Fixtures 作用域为 module :
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
# 定义一个作用域为 module 的 fixture
@pytest.fixture(scope="module")
def my_list():
return [1, 2, 3]
在测试用例中使用 Fixtures :
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import my_list
def test_one(my_list):
my_list.append(4)
assert my_list == [1, 2, 3, 4]
def test_two(my_list):
assert my_list == [1, 2, 3]
def test_three(my_list):
assert my_list == [1, 2, 3]
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 1 items
Config\test_module1.py::test_one PASSED [ 33%]
Config\test_module1.py::test_two FAILED [ 66%]
Config\test_module1.py::test_three FAILED [100%]
===================================================== FAILURES =======================================================
_____________________________________________________ test_two _______________________________________________________
my_list = [1, 2, 3, 4]
def test_two(my_list):
> assert my_list == [1, 2, 3]
E AssertionError: assert [1, 2, 3, 4] == [1, 2, 3]
E
E Left contains one more item: 4
E
E Full diff:
E [
E 1,
E 2,...
E
E ...Full output truncated (3 lines hidden), use '-vv' to show
Test\test_module1.py:10: AssertionError
_____________________________________________________ test_three ______________________________________________________
my_list = [1, 2, 3, 4]
def test_three(my_list):
> assert my_list == [1, 2, 3]
E AssertionError: assert [1, 2, 3, 4] == [1, 2, 3]
E
E Left contains one more item: 4
E
E Full diff:
E [
E 1,
E 2,...
E
E ...Full output truncated (3 lines hidden), use '-vv' to show
Test\test_module1.py:14: AssertionError
=============================================== short test summary info ==============================================
FAILED Config\test_module1.py::test_two - AssertionError: assert [1, 2, 3, 4] == ...
FAILED Config\test_module1.py::test_three - AssertionError: assert [1, 2, 3, 4] =...
============================================ 2 failed, 1 passed in 0.04s =============================================
由于 Fixtures:my_list 在整个模块中只会初始化一次,执行 test_one 后,my_list 的值:[1, 2, 3, 4] 被缓存,后续执行 test_two、test_three 时,my_list 的值都是:[1, 2, 3, 4] 。
其它作用域的逻辑与 module 一致,这里不再赘述。
使用 yield 进行资源管理
import pytest
import pymysql
from pymysql import cursors
@pytest.fixture
def db_connection():
# 连接到数据库
connection = pymysql.connect(host='IP', user='user', password='password', database='test',
charset='utf8mb4', cursorclass=cursors.DictCursor)
yield connection # 返回数据库连接给测试函数
# 关闭数据库连接,有需要还可以执行数据路清理操作
connection.close()
def test_insert_user(db_connection):
cursor = db_connection.cursor()
cursor.execute("INSERT INTO users (name) VALUES ('Alice')")
db_connection.commit()
cursor.execute("SELECT * FROM users")
result = cursor.fetchall()
assert result == [(1, 'Alice')]
yield 语句不仅返回值,还在测试完成后执行后续的代码,从而实现关闭数据库连接。除了数据库管理,yield 还可以在文件操作、环境变量设置、网络连接管理和模拟对象管理等场景下使用。
传递参数给 Fixtures
使用 mark 标记传递参数到 Fixtures
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
@pytest.fixture
def fixt(request):
"""
获取测试用例的标记数据,并返回
"""
# 获取测试用例标记递的数据
marker = request.node.get_closest_marker("user")
if marker is None:
# 未获取到标记,则返回None
data = None
else:
# 获取到标记,则返回标记数据
data = marker.args[0]
return data
在测试用例中通过 mark 传递数据:
# C:\PythonTest\Test\test_module1.py
import pytest
from Package.fixture_fun import fixt
# 已在 pytest.ini 中配置了 user 标记
@pytest.mark.user(42)
def test_fixt(fixt):
assert fixt == 42
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 1 items
Config\test_module1.py::test_fixt PASSED [100%]
================================================= 1 passed in 0.01s ==================================================
定义 Fixture 时,参数化数据
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
@pytest.fixture(params=["JZY", "one man", pytest.param("a good person", marks=pytest.mark.user)])
def fixt(request):
"""
通过 request.param 获取参数值
"""
return request.param
在测试用例中使用 Fixtures:
# C:\PythonTest\Test\test_module1.py
from Package.fixture_fun import fixt
defdef test_fixt(fixt):
assert (fixt == "JZY") or (fixt == "one man") or (fixt == "a good person")
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 3 items
Config\test_module1.py::test_fixt[JZY] PASSED [ 33%]
Config\test_module1.py::test_fixt[one man] PASSED [ 66%]
Config\test_module1.py::test_fixt[a good person] PASSED [100%]
================================================= 3 passed in 0.01s ==================================================
三个参数就是三个测试,也可以通过 -m user
只执行 a good person
这个参数。
使用 @pytest.mark.parametrize 对 Fixtures 进行参数化
在 fixture_fun.py 中定义 Fixtures :
# C:\PythonTest\Package\fixture_fun.py
import pytest
@pytest.fixture()
def one_param_fixture(request):
"""
通过 request.param 获取参数值
"""
return request.param
@pytest.fixture()
def two_fixture(one_param_fixture):
return one_param_fixture
在测试用例中使用 Fixtures:
# C:\PythonTest\Test\test_module1.py
import pytest
from Package.fixture_fun import one_param_fixture, two_fixture
@pytest.mark.parametrize("one_param_fixture", [2, 3])
def test_one_param_fixture(one_param_fixture, two_fixture):
assert one_param_fixture == two_fixture
python .\app.py ,运行结果:
================================================ test session starts ================================================
platform win32 -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 -- C:\PythonTest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\PythonTest\Config
configfile: pytest.ini
collected 2 items
Config\test_module1.py::test_one_param_fixture[2] PASSED [ 50%]
Config\test_module1.py::test_one_param_fixture[3] PASSED [100%]
================================================= 2 passed in 0.01s ==================================================
依赖 Fixtures 可以实现一些通用操作,比如设置和清理测试环境(清理数据库等)、初始化测试数据、参数化测试、依赖注入、并发测试和自定义测试报告等。
还有一些 Fixtures 的用法没有在文章中说明,感兴趣的读者可以访问 How to use fixtures 了解,里面有很详细的介绍,可以进阶学习。
THEEND
© 转载需要保留原始链接,未经明确许可,禁止商业使用。CC BY-NC-ND 4.0
...