各位观众老爷,晚上好!我是你们的老朋友,今天咱们来聊聊 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
的实用技巧。 拜拜!