Pytest入门系列之pytest的conftest.py

Pytest入门系列之pytest的conftest.py

pytest 的本地插件系统核心文件,用于:
✅ 定义测试夹具(fixtures
✅ 实现自定义 hooks
✅ 加载外部插件
✅ 配置测试环境

📌 特性:目录级作用域,支持嵌套配置

基础用法

创建fixture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# tests/conftest.py
@pytest.fixture
def user_data():
"""
基础用户数据夹具(函数作用域)
每次测试函数调用时都会重新初始化
"""
print("\n[初始化] 创建用户数据...")
data = {"name": "Alice", "age": 30, "email": "[email protected]"}
yield data # 测试用例获取数据的节点
print("\n[清理] 清除用户数据...") # 测试完成后执行

# tests/test_basic.py
def test_user_age(user_data):
"""验证用户年龄是否合法"""
print(f"测试数据:{user_data}") # 打印测试数据
assert user_data["age"] >= 18

def test_user_email(user_data):
"""验证邮箱格式"""
assert "@" in user_data["email"]

🔍 执行过程:

1
2
3
4
5
6
7
8
9
10
11
test_basic.py::test_user_age 
[初始化] 创建用户数据...
测试数据:{'name': 'Alice', 'age': 30, 'email': '[email protected]'}
PASSED
[清理] 清除用户数据...

test_basic.py::test_user_email
[初始化] 创建用户数据...
PASSED
[清理] 清除用户数据...

共享 fixture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tests/conftest.py
@pytest.fixture
def shared_resource():
"""跨多个测试文件共享的资源"""
return {"counter": 0}

# test_file1.py
def test_counter_1(shared_resource):
shared_resource["counter"] += 1
assert shared_resource["counter"] == 1

# test_file2.py
def test_counter_2(shared_resource):
assert shared_resource["counter"] == 0 # 函数作用域重置

🔍 可以看出不管在test_file1还是test_file2中运行均可以得到该值

中级用法

作用域控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# tests/conftest.py
@pytest.fixture(scope="module")
def db_connection():
"""
数据库连接夹具(模块级作用域)
整个测试模块共享同一个连接
"""
print("\n=== 建立数据库连接 ===")
conn = {"status": "connected", "handle": "DB12345"}
yield conn
print("\n=== 关闭数据库连接 ===")

# tests/test_database.py
def test_query_1(db_connection):
"""执行第一个查询"""
print(f"当前连接:{db_connection}")
assert db_connection["status"] == "connected"

def test_query_2(db_connection):
"""执行第二个查询"""
db_connection["last_query"] = "SELECT * FROM users"
assert "last_query" in db_connection

🔍 执行结果

1
2
3
4
5
6
7
8
9
test_database.py::test_query_1 
=== 建立数据库连接 ===
当前连接:{'status': 'connected', 'handle': 'DB12345'}
PASSED

test_database.py::test_query_2
PASSED
=== 关闭数据库连接 ===

参数化 fixture

📌 多浏览器测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# tests/conftest.py
@pytest.fixture(params=["chrome", "firefox", "edge"])
def browser(request):
"""
参数化浏览器驱动
每个参数值都会生成独立的测试用例
"""
print(f"\n启动 {request.param} 浏览器")
driver = {
"type": request.param,
"status": "ready",
"version": "102.0"
}
yield driver
print(f"\n关闭 {request.param} 浏览器")

# tests/test_browser.py
def test_browser_init(browser):
"""验证浏览器初始化状态"""
assert browser["status"] == "ready"
assert browser["version"] == "102.0"

🔍 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test_browser.py::test_browser_init[chrome] 
启动 chrome 浏览器
PASSED
关闭 chrome 浏览器

test_browser.py::test_browser_init[firefox]
启动 firefox 浏览器
PASSED
关闭 firefox 浏览器

test_browser.py::test_browser_init[edge]
启动 edge 浏览器
PASSED
关闭 edge 浏览器

自动夹具

📌 全局环境设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# tests/conftest.py
@pytest.fixture(autouse=True)
def global_setup():
"""
自动应用的全局设置夹具
所有测试用例都会自动使用
"""
print("\n--- 全局前置操作 ---")
yield
print("\n--- 全局后置操作 ---")

# tests/test_auto.py
def test_sample_1():
"""简单测试示例1"""
print("执行测试1")

def test_sample_2():
"""简单测试示例2"""
print("执行测试2")

🔍 执行结果

1
2
3
4
5
6
7
8
9
10
11
test_auto.py::test_sample_1 
--- 全局前置操作 ---
执行测试1
PASSED
--- 全局后置操作 ---

test_auto.py::test_sample_2
--- 全局前置操作 ---
执行测试2
PASSED
--- 全局后置操作 ---

高级用法

动态配置

📌 环境变量注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# tests/conftest.py
def pytest_addoption(parser):
"""添加自定义命令行参数"""
parser.addoption("--env",
action="store",
default="dev",
choices=["dev", "staging", "prod"],
help="设置运行环境")

@pytest.fixture(scope="session")
def api_config(request):
"""根据环境参数生成配置"""
env = request.config.getoption("--env")
configs = {
"dev": {"url": "https://dev.api.com", "timeout": 5},
"staging": {"url": "https://stage.api.com", "timeout": 10},
"prod": {"url": "https://api.com", "timeout": 15}
}
print(f"\n加载 {env} 环境配置")
return configs[env]

# tests/test_api.py
def test_api_endpoint(api_config):
"""验证接口地址"""
assert api_config["url"].startswith("https://")

def test_timeout_setting(api_config):
"""验证超时设置"""
assert api_config["timeout"] > 0

📌 ‌执行命令:

1
pytest --env=prod tests/test_api.py -v

🔍 执行结果

1
2
3
4
加载 prod 环境配置
test_api.py::test_api_endpoint PASSED
test_api.py::test_timeout_setting PASSED

自定义 hooks

📌 这里不做深刻的解释,hooks 会单独章节讲解

1
2
3
4
5
6
7
8
9
10
11
12
# tests/conftest.py
def pytest_runtest_setup(item):
"""在每个测试执行前添加自定义逻辑"""
if "slow" in item.keywords:
print("\n检测到慢速测试,开始监控资源...")

def pytest_configure(config):
"""配置阶段添加自定义标记"""
config.addinivalue_line(
"markers",
"stress: 压力测试标记"
)

工厂模式

用户对象工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# tests/conftest.py
class UserFactory:
"""可定制的用户对象工厂"""
@pytest.fixture
def create_user(self):
"""用户创建模板"""
def _factory(**overrides):
base_user = {
"id": 1000,
"username": "default_user",
"role": "member",
"active": True
}
return {zwnj;**base_user, **zwnj;overrides}
return _factory

@pytest.fixture
def user_factory():
return UserFactory()

# tests/test_factory.py
def test_admin_user(user_factory):
"""创建管理员用户"""
admin = user_factory.create_user(
username="admin",
role="admin"
)
assert admin["role"] == "admin"
assert admin["id"] == 1000 # 继承默认值

def test_inactive_user(user_factory):
"""创建禁用用户"""
user = user_factory.create_user(active=False)
assert user["active"] is False

🔍 执行结果

1
2
test_factory.py::test_admin_user PASSED
test_factory.py::test_inactive_user PASSED

实践总结

分层配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project/
├── conftest.py # 全局基础夹具
│ ├── 数据库连接池
│ └── 配置加载器
├── tests/
│ ├── api/
│ │ └── conftest.py # API测试专用夹具
│ │ ├── 认证令牌
│ │ └── 请求客户端
│ └── ui/
│ └── conftest.py # 界面测试专用
│ ├── 浏览器管理
│ └── 页面对象
└── pytest.ini

夹具依赖图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 典型依赖关系示例
@pytest.fixture
def api_client(config):
"""依赖配置的API客户端"""
return APIClient(config["url"])

@pytest.fixture
def auth_token(api_client):
"""依赖API客户端的认证令牌"""
return api_client.login()

@pytest.fixture
def user_profile(auth_token, api_client):
"""复合依赖的用户档案"""
return api_client.get_profile(auth_token)

总结

通过合理使用 conftest.py,可以实现很多复杂的管理以及测试逻辑的高度复用


Pytest入门系列之pytest的conftest.py
https://dreamshao.github.io/2025/03/21/pytest入门系列6/
作者
Yun Shao
发布于
2025年3月21日
许可协议