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
| @pytest.fixture def user_data(): """ 基础用户数据夹具(函数作用域) 每次测试函数调用时都会重新初始化 """ print("\n[初始化] 创建用户数据...") data = {"name": "Alice", "age": 30, "email": "[email protected]"} yield data print("\n[清理] 清除用户数据...")
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
| @pytest.fixture def shared_resource(): """跨多个测试文件共享的资源""" return {"counter": 0}
def test_counter_1(shared_resource): shared_resource["counter"] += 1 assert shared_resource["counter"] == 1
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
| @pytest.fixture(scope="module") def db_connection(): """ 数据库连接夹具(模块级作用域) 整个测试模块共享同一个连接 """ print("\n=== 建立数据库连接 ===") conn = {"status": "connected", "handle": "DB12345"} yield conn print("\n=== 关闭数据库连接 ===")
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
| @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} 浏览器")
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
| @pytest.fixture(autouse=True) def global_setup(): """ 自动应用的全局设置夹具 所有测试用例都会自动使用 """ print("\n--- 全局前置操作 ---") yield print("\n--- 全局后置操作 ---")
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
| 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]
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
| 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
| 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()
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
,可以实现很多复杂的管理以及测试逻辑的高度复用