使用conftest.py 与从专用模块导入装置

Mic*_*ini 8 python pytest

我最近熟悉了 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 测试套件时的最佳实践?

Dun*_*nes 8

差异并不大,主要取决于偏好。我主要用于conftest.py拉入所需的固定装置,但不直接由您的测试使用。因此,您可能有一个装置可以对数据库执行一些有用的操作,但需要数据库连接才能执行此操作。因此,您db_connection可以在 中使用固定装置conftest.py,然后您的测试只需执行以下操作:

\n

conftest.py

\n
from tests.database_fixtures import db_connection\n\n__all__ = [\'db_connection\']\n
Run Code Online (Sandbox Code Playgroud)\n

tests/database_fixtures.py

\n
import pytest\n\n@pytest.fixture\ndef db_connection():\n    ...\n\n@pytest.fixture\ndef new_user(db_connection):\n    ...\n
Run Code Online (Sandbox Code Playgroud)\n

test/test_user.py

\n
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\n
Run Code Online (Sandbox Code Playgroud)\n

如果您没有db_connection提供或直接导入它,那么 pytest在尝试使用该装置时conftest.py将无法找到该装置。如果您直接导入到测试文件中,则 linter 会抱怨它是未使用的导入。更糟糕的是,有些可能会删除它,并导致您的测试失败。因此,对我来说,提供可用的解决方案是最简单的解决方案。db_connectionnew_userdb_connectiondb_connectionconftest.py

\n

覆盖夹具

\n

一个显着的区别是使用conftest.py. 假设您的目录布局为:

\n
./\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\n
Run Code Online (Sandbox Code Playgroud)\n

conftest.py你可以有:

\n
import pytest\n\n@pytest.fixture\ndef some_value():\n    return \'foo\'\n
Run Code Online (Sandbox Code Playgroud)\n

然后tests/bar/conftest.py你可以有:

\n
import pytest\n\n@pytest.fixture\ndef some_value(some_value):\n    return some_value + \'bar\'\n
Run Code Online (Sandbox Code Playgroud)\n

进行多个conftests允许您覆盖固定装置,同时仍然保持对原始固定装置的访问。所以接下来的测试都会起作用。

\n

tests/test_foo.py

\n
def test_foo(some_value):\n    assert some_value == \'foo\'\n
Run Code Online (Sandbox Code Playgroud)\n

tests/bar/test_foobar.py

\n
def test_foobar(some_value):\n    assert some_value == \'foobar\'\n
Run Code Online (Sandbox Code Playgroud)\n

您仍然可以在没有 的情况下执行此操作conftest.py,但它有点复杂。你需要做类似的事情:

\n
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\'\n
Run Code Online (Sandbox Code Playgroud)\n