为什么我的 pytest 固定装置的函数作用域返回一个对象,而该对象没有在新测试中重置它的类变量?

bea*_*auk 3 python unit-testing fixtures pytest python-3.x

我有一个名为 Person() 的类。它有一个 CURRENT_YEAR 类变量,旨在在该类的所有实例之间共享。

我希望单个模块中的每个测试都能获得一个新的(新的)对象,因为我将夹具的范围限定为“函数”。但是,当我在一个测试函数中更改 CURRENT_YEAR 时(使用更改 Person.CURRENT_YEAR 值的类方法发生这种情况),它会保留到下一个测试函数中。很明显,该对象不会在每次测试中被清除并重新创建。

该装置在 conftest.py 中创建,可供所有测试访问。

最后,我把它全部分解,并移动了一些东西,但仍然看到同样的东西。正如我所期望的那样,Person() 类没有被实例化多次。应该如何创建一个固定装置,以便每个 test_ 函数都有自己的类范围?

我尝试过将测试移动到单独的模块,但没有帮助。

我尝试制作第二个固定装置,它返回一个 Person() 对象。没有不同。

我已经在下面的代码中将其删除,因此希望清楚我正在尝试的内容以及为什么我感到困惑。

项目根/测试/test_temp.py

import os,sys
tests = os.path.dirname(__file__)
project = os.path.dirname(tests)
sys.path.insert(0,project)
import pytest
from app.person import *

def test_current_year_changes(person_fixture):
    import pytest
    p = person_fixture
    print(f"CY is {p.CURRENT_YEAR} and age is {p.get_age()}")
    p.add_years_to_age(20)
    print(f"CY is {p.CURRENT_YEAR} and age is {p.get_age()}")
    assert p.CURRENT_YEAR == 20

def test_current_year_changes2(person_fixture2):
    import pytest
    p = person_fixture2
    print(f"CY is {p.CURRENT_YEAR} and age is {p.get_age()}")
    p.add_years_to_age(20)
    print(f"CY is {p.CURRENT_YEAR} and age is {p.get_age()}")
    assert p.CURRENT_YEAR == 20


@pytest.fixture(scope='function')
def person_fixture():
    p = Person()
    return p

@pytest.fixture(scope='function')
def person_fixture2():
    p = Person()
    return p
Run Code Online (Sandbox Code Playgroud)

项目根目录/app/person.py

class Person(object):

    CURRENT_YEAR = 0

    def __init__(self, name=""):
        self.name = name
        self.birth_year = Person.CURRENT_YEAR

    def add_years_to_age(self, years=1):
        Person.CURRENT_YEAR += years

    def get_age(self):
        return Person.CURRENT_YEAR - self.birth_year
Run Code Online (Sandbox Code Playgroud)

代码看起来两个测试应该是相当独立的。但第二个测试函数显示 CURRENT_YEAR 不是以新的类变量开头。

断言失败,显示是Person.CURRENT_YEAR40,而不是 20

MSe*_*ert 6

固定范围仅定义所修饰的函数何时@pytest.fixture运行。这只是将通用测试代码分解为单独函数的一种方法。

因此,在您的情况下,"function"固定装置将为每个测试函数(使用固定装置)执行该函数,并创建一个Person实例。同样,如果范围是 ,则每个测试模块都会运行一次"module"

这完全按照预期进行。CURRENT_YEAR它不仅按照 pytest 的预期工作,而且还按照您自己的预期工作 - 请记住,您实际上想在不同实例之间共享!

应该如何创建一个固定装置,以便每个 test_ 函数都有自己的类范围?

您确实不应该使用全局或静态变量(类变量只是隐藏在类后面的全局变量),因为它使测试变得非常困难(并使程序变得非线程安全)。另请记住,如果您不提供,pytest 就无法提供重置您的程序的基础设施!想一想:到底应该发生什么?它应该为每个测试函数创建一个新的解释器会话吗?它应该重新加载模块吗?它应该重新加载类定义吗?是否应该将 Person.CURRENT_YEAR 设置为零?

解决这个问题的一种方法是抽象类变量,例如使用环境类(无论如何,当前年份似乎也不适合 Person 类):

class Environment(object):
    def __init__(self):
        self.CURRENT_YEAR = 0

class Person(object):
    def __init__(self, environment, name=""):
        self.environment = environment
        self.name = name
        self.birth_year = self.environment.CURRENT_YEAR

    def add_years_to_age(self, years=1):
        self.environment.CURRENT_YEAR += years

    def get_age(self):
        return self.environment.CURRENT_YEAR - self.birth_year
Run Code Online (Sandbox Code Playgroud)

然后让装置创建一个新的环境和人物实例:

@pytest.fixture(scope='function')
def person_fixture():
    e = Environment()
    p = Person(e)
    return p
Run Code Online (Sandbox Code Playgroud)

此时,您Environment的代码中可能需要一个全局实例,以便不同的Person实例可以共享它。

请注意,如果它只是一个变量,那么这没有多大意义,并且可能最终会为不同的环境变量提供不同的类。如果您的应用程序变得更加复杂,您可能需要考虑依赖注入来管理这种复杂性。


但是,如果您只想CURRENT_YEAR重置使用您的每个函数,person_fixture您也可以在夹具中将其设置为 0:

@pytest.fixture(scope='function')
def person_fixture_with_current_year_reset():
    Person.CURRENT_YEAR = 0
    p = Person()
    return p
Run Code Online (Sandbox Code Playgroud)

目前这应该可行,但在并行运行测试时,您可能会看到随机失败,因为全局变量(和类变量)本质上是非线程安全的。