我最近熟悉了 pytest 以及如何使用它conftest.py来定义在我的测试中自动发现和导入的装置。我很清楚它是如何conftest.py工作的以及如何使用它,但我不确定为什么这在某些基本场景中被认为是最佳实践。
假设我的测试是这样构建的:
tests/
--test_a.py
--test_b.py
Run Code Online (Sandbox Code Playgroud)
正如文档和网络上有关 pytest 的各种文章所建议的,最佳实践是定义一个文件,其中包含一些要在和conftest.py中使用的固定装置。为了更好地组织我的装置,我可能需要以语义上有意义的方式将它们分成单独的文件,例如。、、 然后将它们作为插件导入.test_a.pytest_b.pydb_session_fixtures.pydataframe_fixtures.pyconftest.py
tests/
--test_a.py
--test_b.py
--conftest.py
--db_session_fixtures.py
--dataframe_fixtures.py
Run Code Online (Sandbox Code Playgroud)
在conftest.py我会有:
import pytest
pytest_plugins = ["db_session_fixtures", "dataframe_fixtures"]
Run Code Online (Sandbox Code Playgroud)
我将能够在我的测试用例中无缝地使用db_session_fixtures和 ,而无需任何额外的代码。dataframe_fixtures
虽然这很方便,但我觉得它可能会损害可读性。例如,如果我不按conftest.py上述方式使用,我可能会写成test_a.py
from .dataframe_fixtures import my_dataframe_fixture
def test_case_a(my_dataframe_fixture):
#some tests
Run Code Online (Sandbox Code Playgroud)
并照常使用固定装置。
缺点是它需要我导入固定装置,但显式导入提高了测试用例的可读性,让我一目了然地知道固定装置来自哪里,就像任何其他 python 模块一样。
我是否忽略了这个解决方案的缺点或其他conftest.py带来的优点,使其成为设置 pytest 测试套件时的最佳实践?
差异并不大,主要取决于偏好。我主要用于conftest.py拉入所需的固定装置,但不直接由您的测试使用。因此,您可能有一个装置可以对数据库执行一些有用的操作,但需要数据库连接才能执行此操作。因此,您db_connection可以在 中使用固定装置conftest.py,然后您的测试只需执行以下操作:
conftest.py
from tests.database_fixtures import db_connection\n\n__all__ = [\'db_connection\']\nRun Code Online (Sandbox Code Playgroud)\ntests/database_fixtures.py
import pytest\n\n@pytest.fixture\ndef db_connection():\n ...\n\n@pytest.fixture\ndef new_user(db_connection):\n ...\nRun Code Online (Sandbox Code Playgroud)\ntest/test_user.py
from tests.database_fixtures import new_user\n\ndef test_user(new_user):\n assert new_user.id > 0 # or whatever the test needs to do\nRun Code Online (Sandbox Code Playgroud)\n如果您没有db_connection提供或直接导入它,那么 pytest在尝试使用该装置时conftest.py将无法找到该装置。如果您直接导入到测试文件中,则 linter 会抱怨它是未使用的导入。更糟糕的是,有些可能会删除它,并导致您的测试失败。因此,对我来说,提供可用的解决方案是最简单的解决方案。db_connectionnew_userdb_connectiondb_connectionconftest.py
一个显着的区别是使用conftest.py. 假设您的目录布局为:
./\n\xe2\x94\x9c\xe2\x94\x80 conftest.py\n\xe2\x94\x94\xe2\x94\x80 tests/\n \xe2\x94\x9c\xe2\x94\x80 test_foo.py\n \xe2\x94\x94\xe2\x94\x80 bar/\n \xe2\x94\x9c\xe2\x94\x80 conftest.py\n \xe2\x94\x94\xe2\x94\x80 test_foobar.py\nRun Code Online (Sandbox Code Playgroud)\n在conftest.py你可以有:
import pytest\n\n@pytest.fixture\ndef some_value():\n return \'foo\'\nRun Code Online (Sandbox Code Playgroud)\n然后tests/bar/conftest.py你可以有:
import pytest\n\n@pytest.fixture\ndef some_value(some_value):\n return some_value + \'bar\'\nRun Code Online (Sandbox Code Playgroud)\n进行多个conftests允许您覆盖固定装置,同时仍然保持对原始固定装置的访问。所以接下来的测试都会起作用。
\ntests/test_foo.py
def test_foo(some_value):\n assert some_value == \'foo\'\nRun Code Online (Sandbox Code Playgroud)\ntests/bar/test_foobar.py
def test_foobar(some_value):\n assert some_value == \'foobar\'\nRun Code Online (Sandbox Code Playgroud)\n您仍然可以在没有 的情况下执行此操作conftest.py,但它有点复杂。你需要做类似的事情:
import pytest\n\n# in this scenario we would have something like:\n# mv contest.py tests/custom_fixtures.py\nfrom tests.custom_fixtures import some_value as original_some_value\n\n@pytest.fixture\ndef some_value(original_some_value):\n return original_some_value + \'bar\'\n\ndef test_foobar(some_value):\n assert some_value == \'foobar\'\nRun Code Online (Sandbox Code Playgroud)\n