conftest.py在Pytest中有什么作用?

550
我正在努力理解conftest.py文件的用途。
在我的(目前很小的)测试套件中,我在项目根目录下有一个conftest.py文件。我使用它来定义我注入到测试中的fixture。
我有两个问题:
1. 这是conftest.py的正确使用方式吗?它还有其他用途吗? 2. 我可以有多个conftest.py文件吗?什么时候需要这样做?如果能提供示例将不胜感激。
更一般地说,在pytest测试套件中,你如何定义conftest.py文件的目的和正确使用方式?

69
你让我感到“很棒”。不过,我觉得文档可以更好一些。 - user9074332
62
是的,文档可以更好。我搜索了整个pytest文档以查找“conftest.py”,虽然文档中有很多关于使用conftest文件进行此操作或执行那个操作的引用,但文档中从未指出当pytest进行测试发现时,将在(测试发现正在进行的目录结构内)运行所有找到的conftest.py文件(在运行任何测试之前,在测试集合阶段)。不得不通过实验自己弄清楚这一点。 - Daniel Goldfarb
6个回答

638

这是正确使用conftest.py吗?

是的,这是正确的用法。Fixture(夹具)是conftest.py的常见用途。您定义的Fixture将在测试套件中的所有测试之间共享。但是,在根conftest.py中定义Fixture可能是无用的,并且如果这些Fixture未被所有测试使用,则会减慢测试速度。

它还有其他用途吗?

是的,它有其他用途。

  • Fixture(夹具):为测试使用的静态数据定义Fixture。除非另有指定,否则所有测试都可以访问此数据。这也可以是模块的辅助数据和helpers,将传递给所有测试。

  • 外部插件加载:使用conftest.py导入外部插件或模块。通过定义以下全局变量,pytest将加载该模块并使其在测试中可用。插件通常是在您的项目中定义的文件或其他模块,可能需要在您的测试中使用。您还可以像此处所述加载一组预定义插件。

    pytest_plugins = "someapp.someplugin"

  • Hooks(钩子):您可以指定setup和teardown方法等钩子,以改进测试。有关可用的hooks集,请阅读Hooks链接。例如:

      def pytest_runtest_setup(item):
           """ called before ``pytest_runtest_call(item). """
           #do some stuff`
    
  • 测试根路径:这是一个有点隐藏的功能。通过在您的根路径中定义conftest.py,您将使pytest识别您的应用程序模块而无需指定PYTHONPATH。在后台,py.test通过包括从根路径找到的所有子模块来修改您的sys.path

我可以拥有多个conftest.py文件吗?

是的,如果您的测试结构有些复杂,则强烈建议这样做。conftest.py文件具有目录范围。因此,创建有针对性的fixture和helper是一个好习惯。

那么我什么时候需要这样做?可以举个例子吗?

有几种情况可能适用:

为特定测试组创建一组工具或钩子(hooks)

root/mod/conftest.py

def pytest_runtest_setup(item):
    print("I am mod")
    #do some stuff


test root/mod2/test.py will NOT produce "I am mod"

为某些测试加载一组fixtures,但不为其他测试加载。

root/mod/conftest.py

@pytest.fixture()
def fixture():
    return "some stuff"

根目录/mod2/conftest.py

@pytest.fixture()
def fixture():
    return "some other stuff"

根目录/mod2/test.py

def test(fixture):
    print(fixture)

将打印“some other stuff”。

覆盖从根conftest.py继承的挂钩。

root/mod/conftest.py

def pytest_runtest_setup(item):
    print("I am mod")
    #do some stuff

根目录/conftest.py

def pytest_runtest_setup(item):
    print("I am root")
    #do some stuff

root/mod中运行任何测试,只会打印出“I am mod”。

你可以在这里阅读有关conftest.py的更多信息。

编辑:

如果我需要从不同模块的多个测试调用普通的帮助程序函数-如果我将它们放在conftest.py中,它们是否可用?或者我应该将它们放在helpers.py模块中并在测试模块中导入和使用它们?

您可以使用conftest.py定义您的帮助程序。但是,您应该遵循常见做法。至少在pytest中,可以将帮助程序用作固件。例如,在我的测试中,我有一个模拟redis helper,我以这种方式注入到我的测试中。

root/helper/redis/redis.py

@pytest.fixture
def mock_redis():
    return MockRedis()

根目录/tests/stuff/conftest.py

pytest_plugin="helper.redis.redis"

根目录/tests/stuff/test.py

def test(mock_redis):
    print(mock_redis.get('stuff'))

这是一个测试模块,您可以在测试中自由导入。请注意,如果您的模块redis包含更多测试,则可以将redis.py命名为conftest.py。但是,由于存在歧义,不建议这样做。

如果您想使用conftest.py,只需将该助手放在根目录下的conftest.py中,并在需要时注入即可。

根目录/tests/conftest.py

@pytest.fixture
def mock_redis():
    return MockRedis()

根目录 / 测试 / 杂项 / test.py

def test(mock_redis):
    print(mock_redis.get(stuff))

你可以做的另一件事是编写一个可安装的插件。在这种情况下,你的辅助程序可以在任何地方编写,但需要定义一个入口点以便在你的和其他可能的测试框架中安装。有关详细信息,请参见 此处

如果你不想使用 fixture,则当然可以定义一个简单的 helper,并在需要它的任何地方使用普通的导入。

root/tests/helper/redis.py

class MockRedis():
    # stuff

根目录/测试/材料/测试.py

from helper.redis import MockRedis

def test():
    print(MockRedis().get(stuff))
然而,由于模块不在测试的子文件夹中,您可能会遇到路径问题。通过将__init__.py添加到您的helper中,您应该能够克服这个问题(未经测试)。 root/tests/helper/init.py
from .redis import MockRedis

或者将辅助模块添加到您的 PYTHONPATH 中。


没有 conftest.py,你的测试根路径不会被添加到 sys.path 中,你会看到类似于“ModuleNotFoundError: No module named 'foobar'”的错误。 - Cale Sweeney
哇,多么详细的答案啊... 非常感谢! - Gaurav Khurana
14
еҘҪзҡ„еӣһзӯ”пјҒ иҝҷеә”иҜҘжҳҜpytestж–ҮжЎЈдёӯзҡ„conftest.pyйЎөйқўпјҒ - Simeon Nedkov
这非常有帮助...但是我对“py.test通过包含从根路径找到的所有子模块来修改您的sys.path”这一语句有点困惑。在根路径下有两个目录,tests和src。src下有一个名为“core”的目录/包,其中包含.py文件。通过检查测试期间__main__.py文件中的sys.path,我发现虽然根目录和“tests”目录被列出,但“src”和“src/core”都没有被列出...这是有问题的,因为这意味着__main__.py无法像正常运行时(=非测试)那样导入同级.py文件。 - mike rodent
我认为你打印错误了 pytest_plugin="helper.redis.redis"应该是 pytest_plugins - Oleksandr Spazhev
显示剩余4条评论

18

在广义上,conftest.py是一个本地的每个目录插件。在这里,您可以定义特定于目录的钩子和固定装置。在我的情况下,我有一个根目录,其中包含特定于项目的测试目录。一些通用的魔法在“root”conftest.py中驻扎。项目特定的内容则在它们自己的文件中定义。在conftest.py中存储装置,除非它们没有被广泛使用(在这种情况下,我更喜欢直接在测试文件中定义)。


16

我使用conftest.py文件定义fixture,并注入到我的测试中,这是conftest.py的正确用法吗?

是的,fixture通常用于为多个测试准备数据。

它还有其他用途吗?

是的,fixture是一个在实际测试函数之前(有时在之后)由pytest运行的函数。fixture中的代码可以进行任何你想要的操作。例如,fixture可以用于为测试准备数据集,也可以在运行测试之前将系统置于已知状态。

我可以有多个conftest.py文件吗?什么时候需要这样做?

首先,将fixture放入单个测试文件中是可能的。然而,要在多个测试文件之间共享fixture,你需要使用一个位于所有测试的中心位置的conftest.py文件。fixture可以被任何测试共享。如果你想要fixture仅被该文件中的测试使用,可以将其放在单独的测试文件中。

其次,是的,你可以在顶级测试目录的子目录中拥有其他conftest.py文件。如果这样做,这些较低级别的conftest.py文件中定义的fixture将可用于该目录和子目录中的测试。

最后,在测试根目录下的 conftest.py 文件中添加固件将使它们在所有测试文件中可用。


15

更新 Pytest 7.2(2023年3月)

添加此答案是因为有一些评论指出文档中缺少信息,我猜这是在问题最初提出时发生的,但现在已经得到了很好的解释和记录。

conftest.py:在多个文件之间共享 fixture到页面底部(实际上与 fixture 或设置 pytest 相关的任何内容都可以应用于 conftest.py)都与 conftest.py 相关。

因此,通过快速扫描文档,以下是主要功能(但不包括):

  • 为测试提供固定装置(无论装置范围如何)
  • 将可用的装置模块化为每个conftest.py中的树形层次结构
  • 范围:在类、模块、包或会话之间共享装置
  • conftest.py可以覆盖conftest.py(但反之不行)请参见此处
  • 第三方插件提供装置
  • 加载和共享多个测试之间的测试数据
  • 使用其他项目的装置
  • 提供和共享钩子

现在在被接受的答案中,它说测试根路径:这是一个有点隐藏的功能。通过定义...但是目前为止,最好的方法是将pythonpath添加到项目根目录下的pytest.ini中(实际项目根目录,而不是project/tests),请参见:pythonpath,它可以为您完成所有操作并更加强大。

看起来像:

[pytest]
pythonpath = src1 src2

最后,我在这里放置了pytest文档中的许多图表之一,关于作用域的边界可以像这样可视化:

enter image description here


1
非常好的总结,谢谢。这个答案应该排名更高。 - armara

11
以下是关于使用 conftest.py 共享 fixture 的官方文档:

conftest.py:跨多个文件共享 fixtures

conftest.py 文件用作为整个目录提供 fixtures 的一种手段。在 conftest.py 中定义的 fixtures 可以被该 package 中的所有测试使用,无需导入它们(pytest 会自动发现它们)。

您可以拥有多个嵌套的目录/包来包含您的测试,每个目录都可以具有自己的 conftest.py 文件和其自己的 fixtures,添加到父目录中的 conftest.py 文件提供的 fixtures 中。


1
你可以有多个conftest.py
例如,tests1/test.py 只能访问如下所示的 tests1/conftest.py
project
 |-tests1
 |  |-__init__.py
 |  |-conftest.py # <- 〇
 |  |-test.py # Here
 |  └-subtests
 |     |-__init__.py
 |     |-conftest.py # <- ✖
 |     └-subtest.py
 └-tests2
    |-__init__.py
    |-conftest.py # <- ✖
    |-test.py
    └-subtests
       |-__init__.py
       |-conftest.py # <- ✖
       └-subtest.py

而且,tests1/subtests/subtest.py 只能访问下面显示的 tests1/subtests/conftest.pytests1/conftest.py。* 如果在 tests1/subtests/conftest.pytests1/conftest.py 中有相同名称的 fixture(函数),那么 tests1/subtests/subtest.py 将优先访问 tests1/subtests/conftest.py 中的 fixture(函数):
project
 |-tests1
 |  |-__init__.py
 |  |-conftest.py # <- 〇
 |  |-test.py 
 |  └-subtests
 |     |-__init__.py
 |     |-conftest.py # <- 〇
 |     └-subtest.py # Here
 └-tests2
    |-__init__.py
    |-conftest.py # <- ✖
    |-test.py
    └-subtests
       |-__init__.py
       |-conftest.py # <- ✖
       └-subtest.py

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接