各位观众老爷,晚上好!我是你们的老朋友,今天咱们来聊聊 pytest 的 conftest.py,这玩意儿就像个百宝箱,能帮你共享 fixture 和配置,让你的测试代码更加优雅高效。
为啥需要 conftest.py?
想象一下,你有很多测试文件,每个文件都需要用到一些相同的 fixture,比如数据库连接、API 客户端等等。如果每个文件都定义一遍这些 fixture,那简直就是一场噩梦,代码重复不说,维护起来也痛苦不堪。
这时候,conftest.py 就闪亮登场了。它可以让你把这些通用的 fixture 和配置放在一个地方,然后各个测试文件就可以直接使用了,简直不要太方便!
conftest.py 的工作原理
pytest 在运行测试时,会自动查找当前目录和所有父目录下的 conftest.py 文件。它会加载这些文件,然后把里面定义的 fixture 和配置都注册到测试环境中。
简单来说,就是 pytest 会先扫一遍你项目里的 conftest.py 文件,把里面的宝贝都收起来,然后测试文件就可以随意调用了。
conftest.py 的用法详解
咱们先建一个简单的项目目录结构:
my_project/
├── conftest.py
├── test_module1.py
└── test_module2.py
conftest.py: 存放共享fixture和配置的文件test_module1.py: 测试模块 1test_module2.py: 测试模块 2
1. 定义 fixture
在 conftest.py 中,我们可以定义各种各样的 fixture。
# conftest.py
import pytest
@pytest.fixture
def my_fixture():
"""
这是一个简单的 fixture,返回一个字符串。
"""
return "Hello from conftest.py!"
@pytest.fixture(scope="module") # scope 决定了 fixture 的生命周期
def module_fixture():
"""
一个 module 级别的 fixture,每个 module 只会被调用一次。
"""
print("nModule fixture setup") # setup
yield "Module data"
print("nModule fixture teardown") # teardown
@pytest.fixture(params=[1, 2, 3])
def parameterized_fixture(request):
"""
一个参数化的 fixture,pytest 会自动运行三次测试,每次使用不同的参数。
"""
return request.param
解释:
@pytest.fixture: 这是定义fixture的装饰器。my_fixture: 一个简单的fixture,返回一个字符串。scope="module":scope参数决定了fixture的生命周期。module表示这个fixture在每个模块(.py文件)中只会被调用一次。其他的 scope 包括"function"(默认值,每个测试函数调用一次),"class"(每个类调用一次),"session"(整个测试会话调用一次)。yield:fixture可以使用yield关键字来定义 setup 和 teardown 逻辑。在yield之前的代码会在测试函数执行之前运行(setup),yield之后的代码会在测试函数执行之后运行(teardown)。params=[1, 2, 3]:params参数定义了参数化fixture的参数列表。pytest会自动运行多次测试,每次使用不同的参数。request: 在参数化fixture中,可以使用request对象来获取当前的参数值。
2. 使用 fixture
在测试文件中,我们可以直接使用 conftest.py 中定义的 fixture。
# test_module1.py
def test_my_fixture(my_fixture):
"""
测试 my_fixture。
"""
assert my_fixture == "Hello from conftest.py!"
def test_module_fixture(module_fixture):
"""
测试 module_fixture。
"""
assert module_fixture == "Module data"
def test_parameterized_fixture(parameterized_fixture):
"""
测试 parameterized_fixture.
"""
print(f"nTesting with parameter: {parameterized_fixture}")
assert parameterized_fixture in [1, 2, 3]
# test_module2.py
def test_my_fixture_again(my_fixture):
"""
再次测试 my_fixture。
"""
assert my_fixture == "Hello from conftest.py!"
def test_module_fixture_again(module_fixture):
"""
再次测试 module_fixture.
"""
assert module_fixture == "Module data"
解释:
- 直接在测试函数的参数列表中声明
fixture的名字,pytest就会自动把对应的fixture注入到测试函数中。
3. 运行测试
在项目根目录下运行 pytest 命令:
pytest
你会看到类似这样的输出:
============================= test session starts ==============================
platform darwin -- Python 3.9.7, pytest-7.1.2, pluggy-1.0.0
rootdir: /path/to/my_project
collected 7 items
test_module1.py::test_my_fixture PASSED [ 14%]
test_module1.py::test_module_fixture
Module fixture setup
PASSED [ 28%]
test_module1.py::test_parameterized_fixture[1]
Testing with parameter: 1
PASSED [ 42%]
test_module1.py::test_parameterized_fixture[2]
Testing with parameter: 2
PASSED [ 57%]
test_module1.py::test_parameterized_fixture[3]
Testing with parameter: 3
PASSED [ 71%]
test_module2.py::test_my_fixture_again PASSED [ 85%]
test_module2.py::test_module_fixture_again PASSED [100%]
Module fixture teardown
============================== 7 passed in 0.04s ===============================
注意:
Module fixture setup和Module fixture teardown只被打印了一次,说明module_fixture的作用域是module级别的。test_parameterized_fixture被运行了三次,每次使用不同的参数。
4. 配置选项
conftest.py 还可以用来配置 pytest 的行为。
# conftest.py
def pytest_addoption(parser):
"""
添加命令行选项。
"""
parser.addoption(
"--env",
action="store",
default="dev",
help="运行环境 (dev, test, prod)",
)
@pytest.fixture
def env(request):
"""
获取命令行选项的值。
"""
return request.config.getoption("--env")
def pytest_configure(config):
"""
在测试运行开始时执行一些配置操作。
"""
config.addinivalue_line(
"markers", "slow: marks tests as slow (deselect with '-m "not slow"')"
)
解释:
pytest_addoption: 这个 hook 函数用于添加命令行选项。--env: 添加一个名为--env的命令行选项,默认值为dev,用于指定运行环境。
env: 一个fixture,用于获取命令行选项--env的值。pytest_configure: 这个 hook 函数在测试运行开始时执行。config.addinivalue_line("markers", "slow: ..."): 添加一个名为slow的 marker。
使用:
-
运行测试时,可以使用
--env选项来指定运行环境:pytest --env test -
可以使用
pytest.mark.slow装饰器来标记慢速测试:# test_module1.py import pytest @pytest.mark.slow def test_slow_function(): """ 这是一个慢速测试。 """ import time time.sleep(2) assert True然后可以使用
-m选项来选择或排除带有特定 marker 的测试:pytest -m "slow" # 只运行标记为 slow 的测试 pytest -m "not slow" # 排除标记为 slow 的测试
5. conftest.py 的位置
pytest 会查找当前目录和所有父目录下的 conftest.py 文件。这意味着,你可以把 conftest.py 放在项目根目录下,这样所有的测试文件都可以访问里面的 fixture 和配置。
或者,你也可以把 conftest.py 放在某个子目录下,这样只有该目录及其子目录下的测试文件才能访问里面的 fixture 和配置。这可以帮助你更好地组织和管理你的 fixture 和配置。
6. fixture 的覆盖
如果多个 conftest.py 文件中定义了同名的 fixture,pytest 会按照以下优先级来选择使用哪个 fixture:
- 测试文件所在的目录下的
conftest.py - 测试文件所在的目录的父目录下的
conftest.py - 以此类推,直到项目根目录下的
conftest.py
这意味着,你可以在子目录下的 conftest.py 中覆盖父目录下的 fixture,从而为特定的测试文件提供定制的 fixture。
示例:
my_project/
├── conftest.py (全局配置)
├── test_module1.py
└── sub_directory/
├── conftest.py (子目录特定配置)
└── test_module2.py
如果 sub_directory/conftest.py 中定义了一个与项目根目录下的 conftest.py 中同名的 fixture,那么 sub_directory/test_module2.py 会使用 sub_directory/conftest.py 中定义的 fixture。而 test_module1.py 仍然会使用项目根目录下的 conftest.py 中定义的 fixture。
总结
conftest.py 是 pytest 中一个非常强大的工具,可以帮助你共享 fixture 和配置,让你的测试代码更加简洁、可维护。掌握了 conftest.py 的用法,你的 pytest 水平就能更上一层楼。
表格总结:conftest.py 常用功能
| 功能 | 描述 | 示例 |
|---|---|---|
定义共享 fixture |
在多个测试文件中共享 fixture,避免代码重复。 |
python # conftest.py import pytest @pytest.fixture def db_connection(): return "Database Connection" python # test_module.py def test_db(db_connection): assert db_connection == "Database Connection" |
配置 pytest |
添加命令行选项、注册 marker 等,定制 pytest 的行为。 |
python # conftest.py def pytest_addoption(parser): parser.addoption("--verbose", action="store_true", help="增加详细输出") def pytest_configure(config): if config.getoption("--verbose"): print("详细模式已开启") |
| 定义 hook 函数 | pytest 提供的 hook 函数,可以在测试的不同阶段执行自定义逻辑。 |
python # conftest.py def pytest_sessionfinish(session, exitstatus): print(f"测试会话结束,退出状态码:{exitstatus}") |
fixture 作用域 |
控制 fixture 的生命周期,例如 function、module、class、session 级别。 |
python # conftest.py import pytest @pytest.fixture(scope="module") def module_data(): return "Module Data" |
参数化 fixture |
让 fixture 返回不同的值,从而运行多次测试,每次使用不同的参数。 |
python # conftest.py import pytest @pytest.fixture(params=[1, 2, 3]) def number(request): return request.param python # test_module.py def test_number(number): print(f"测试数字:{number}") assert number > 0 |
覆盖 fixture |
在子目录下的 conftest.py 中覆盖父目录下的 fixture,为特定的测试文件提供定制的 fixture。 |
(见上文示例) |
希望今天的讲座能帮到大家! 以后有机会再和大家分享更多 pytest 的实用技巧。 拜拜!