何时使用 pytest 固定装置?

the*_*man 7 unit-testing decorator pytest python-3.x

我是测试新手,我偶然发现了 pytest 固定装置,但我不完全确定何时使用它们以及它们为什么有用。

例如,请参阅以下代码:

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0
Run Code Online (Sandbox Code Playgroud)

这里pytest.fixture的作用是什么?为什么我们不能简单地创建一个被调用的函数input_value()并在测试函数内运行该函数?例如:

import pytest

def input_value():
   input = 39
   return input

def test_divisible_by_3():
   assert input_value() % 3 == 0

def test_divisible_by_6():
   assert input_value() % 6 == 0
Run Code Online (Sandbox Code Playgroud)

为什么我们不能这样做?使用fixture有什么用?

Mic*_*ert 26

Pytest Fixtures 和常规函数都可以用来构建测试代码并减少代码重复。乔治·乌多森(George Udosen)提供的答案很好地解释了这一点。

然而,OP 特别询问了 a 和常规 Python 函数之间的差异pytest.fixture,并且存在许多差异:

Pytest Fixtures 是有范围的

默认情况下,pytest.fixture每个引用夹具的测试函数都会执行 a 。但在某些情况下,夹具设置可能在计算上昂贵或耗时,例如初始化数据库。为此,pytest.fixture可以将 a 配置为更大的范围。这允许pytest.fixture在模块(模块范围)中的测试之间或什至在 pytest 运行(会话范围)的所有测试之间重用。以下示例使用模块范围的固定装置来加速测试:

from time import sleep
import pytest


@pytest.fixture(scope="module")
def expensive_setup():
    return sleep(10)

def test_a(expensive_setup):
    pass  # expensive_setup is instantiated for this test

def test_b(expensive_setup):
    pass  # Reuses expensive_setup, no need to wait 10s
Run Code Online (Sandbox Code Playgroud)

尽管可以通过常规函数调用来实现不同的作用域,但作用域固定装置使用起来更加愉快。

Pytest Fixtures 基于依赖注入

Pytest 在测试收集阶段注册所有固定装置。当测试函数需要名称与注册的固定装置名称匹配的参数时,Pytest 将负责为测试实例化固定装置并将实例提供给测试函数。这是依赖注入的一种形式

与常规函数相比的优点是您可以通过pytest.fixture名称引用任何函数,而无需显式导入它。例如,Pytest 附带了一个tmp_path固定装置,任何测试都可以使用它来处理临时文件。以下示例取自Pytest 文档

CONTENT = "content"


def test_create_file(tmp_path):
    d = tmp_path / "sub"
    d.mkdir()
    p = d / "hello.txt"
    p.write_text(CONTENT)
    assert p.read_text() == CONTENT
    assert len(list(tmp_path.iterdir())) == 1
    assert 0
Run Code Online (Sandbox Code Playgroud)

用户使用前无需导入,tmp_path非常方便。

甚至可以在测试功能没有请求的情况下将夹具应用于测试功能(请参阅自动使用夹具)。

Pytest 夹具可以参数化

与测试参数化 非常相似,夹具参数化允许用户指定夹具的多个“变体”,每个变体具有不同的返回值。使用该夹具的每个测试都将执行多次,每个变体一次。假设您想要测试所有代码是否都针对 HTTP 和 HTTPS URL 进行了测试,您可以执行以下操作:

import pytest


@pytest.fixture(params=["http", "https"])
def url_scheme(request):
    return request.param


def test_get_call_succeeds(url_scheme):
    # Make some assertions
    assert True
Run Code Online (Sandbox Code Playgroud)

参数化夹具将导致每个引用测试都使用每个版本的夹具执行:

$ pytest
tests/test_fixture_param.py::test_get_call_succeeds[http] PASSED                                                                                                                                                                         [ 50%]
tests/test_fixture_param.py::test_get_call_succeeds[https] PASSED                                                                                                                                                                        [100%]

======== 2 passed in 0.01s ========
Run Code Online (Sandbox Code Playgroud)

结论

与常规函数调用相比,Pytest 装置提供了许多生活质量的改进。我建议始终优先选择 Pytest 固定装置而不是常规函数,除非您必须能够直接调用固定装置。直接调用 pytest 装置是不符合预期的,并且调用将失败。

  • 我没有看到固定装置的生活质量得到改善,只是需要付出额外的努力才能让它们正常工作。例如,引用一个固定装置而不需要导入它会更糟糕,代码被组织成模块是有原因的;这也破坏了自动完成和类型检查。参数化可以通过将测试的参数传递给常规函数来完成。范围可以通过类或模块全局变量来实现。我缺少常规函数的缺点吗? (15认同)
  • “相对于常规函数的优点是,您可以通过名称引用任何 pytest.fixture,而无需显式导入它”。当查看其他人的代码时,会更难追踪固定装置的定义位置。 (7认同)
  • 无需 pytest 即可实现相同的功能。此外,您不必使用所有功能,例如自动使用装置。如果使用常规函数对您来说效果更好:那就去做吧 不过,值得一提的是,您最终可能会构建自己的测试框架。新接触项目的开发人员必须花时间学习框架,而 pytest 已经广为人知 [1][2]。除了固定装置之外,pytest 还有其他很酷的功能:) [1] https://www.jetbrains.com/lp/python-developers-survey-2019/ [2] https://www.jetbrains.com/lp/python- 2020 年开发商调查/ (3认同)

Geo*_*sen 3

我自己对 pytest 很陌生,但我知道它减少了编写多次测试多次使用的代码的需要,因为在您的情况下,您需要分别重写该函数,并且此代码片段将是一个入门:

Fixtures 用于向测试提供一些数据,例如数据库连接、要测试的 URL 和某种输入数据。

因此,我们可以将固定功能附加到测试中,而不是为每个测试运行相同的代码,它会在执行每个测试之前运行并将数据返回到测试。

-- 来源: https: //www.tutorialspoint.com/pytest/pytest_fixtures.htm

一般来说,它有助于在测试之间共享通用的资源,并大大减少重复。同样,这些固定功能的返回值可以作为“输入参数”传递到各个测试中,如下所示:

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0
Run Code Online (Sandbox Code Playgroud)

  • 但为什么要使用 pytest 夹具呢?为什么我们不能定义一个与 pytest 夹具具有相同返回值的普通函数? (7认同)
  • 和@theman同样的问题,为什么?如果没有“@pytest.fixture”,普通函数就可以做到,并且也可以共享重用。我用谷歌搜索了一下,但仍然很少有人解释为什么不使用普通函数来共享数据输入。 (5认同)
  • 我需要多次重写该函数是什么意思?我只需要编写一次,然后在测试函数中调用它? (2认同)