Python测试治具简介

示例

有时,更复杂的测试需要在运行要测试的代码之前进行一些设置。可以在测试功能本身中执行此操作,但是最终您将不得不执行大型测试功能,以至于很难分辨设置在何处停止并开始测试。您还可以在各种测试功能之间获得大量重复的设置代码。

我们的代码文件:

# projectroot /模块/stuff.py
class Stuff(object):
    def prep(self):
       self.foo= 1
       self.bar= 2

我们的测试文件:

# projectroot / tests / test_stuff.py
import pytest
from module import stuff


def test_foo_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

这些是非常简单的示例,但是如果我们的Stuff对象需要更多的设置,它将变得笨拙。我们看到测试用例之间存在一些重复的代码,因此让我们首先将其重构为一个单独的函数。

# projectroot / tests / test_stuff.py
import pytest
from module import stuff


def get_prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff


def test_foo_updates():
    my_stuff = get_prepped_stuff()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = get_prepped_stuff()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

这看起来更好,但是我们的调用仍然使测试功能混乱。my_stuff = get_prepped_stuff()

py.test装置可以解救!

夹具是测试设置功能的更强大,更灵活的版本。他们可以做的事情比我们在这里要利用的要多得多,但是我们一次只能迈出一步。

首先,我们将其更改get_prepped_stuff为prepped_stuff。您想用名词而不是动词来命名设备,因为这些设备最终将如何在以后的测试功能中使用。在@pytest.fixture表明这个特定功能应该作为夹具,而不是常规的函数来处理。

@pytest.fixture
def prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff

现在我们应该更新测试功能,以便它们使用夹具。这是通过在其定义中添加一个与灯具名称完全匹配的参数来完成的。当py.test执行时,它将会运行在运行测试之前夹具,再通过固定的返回值将通过参数测试功能。(请注意,固定装置不需要返回值;它们可以执行其他设置操作,例如调用外部资源,在文件系统上进行设置,将值放入数据库中,无论设置需要进行何种测试)

def test_foo_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

现在您可以看到为什么我们使用名词来命名它了。但是该my_stuff = prepped_stuff行几乎没有用,所以让我们prepped_stuff直接使用它即可。

def test_foo_updates(prepped_stuff):
    assert 1 == prepped_stuff.foo
    prepped_stuff.foo = 30000
    assert prepped_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    assert 2 == prepped_stuff.bar
    prepped_stuff.bar = 42
    assert 42 == prepped_stuff.bar

现在我们正在使用灯具!我们可以通过更改灯具的范围来进行进一步操作(因此,它仅在每个测试模块或测试套件执行会话中运行一次,而不是每个测试功能一次),构建使用其他灯具的灯具,对灯具进行参数化(以便灯具和所有灯具使用该灯具的测试会运行多次,对于给灯具的每个参数一次),灯具会从调用它们的模块中读取值……如前所述,灯具比普通的设置功能具有更大的功能和灵活性。

测试完成后进行清理。

假设我们的代码已经增长,我们的Stuff对象现在需要进行特殊清理。

# projectroot /模块/stuff.py
class Stuff(object):
def prep(self):
   self.foo= 1
   self.bar= 2

def finish(self):
   self.foo= 0
   self.bar= 0

我们可以在每个测试功能的底部添加一些代码来调用清除操作,但是固定装置提供了一种更好的方法来执行此操作。如果您向灯具添加一个功能并将其注册为终结器,则在使用灯具进行测试之后,将调用终结器函数中的代码。如果fixture的范围大于单个功能(例如模块或会话),则完成器将在范围内的所有测试完成后执行,因此在模块运行完毕后或在整个测试运行会话结束时将执行终结器。

@pytest.fixture
def prepped_stuff(request):  # 我们需要传递使用终结器的请求
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    def fin():  # 终结函数
        # 在这里进行所有清理
        my_stuff.finish()
    request.addfinalizer(fin)  # 将fin()注册为终结器
    # 如果您确实想在这里进行更多设置
    return my_stuff

乍一看,在函数内部使用finalizer函数可能有点难以理解,尤其是当您拥有更复杂的灯具时。您可以改为使用yield固定装置,以更易于理解的执行流程来执行相同的操作。唯一真正的区别是,在设置完成的固定装置部分return使用而不是使用a yield,并且控件应转到测试功能,然后在之后添加所有清除代码yield。我们还将其装饰为,yield_fixture以便py.test知道如何处理它。

@pytest.yield_fixture
def prepped_stuff():  # 现在不需要请求!
    # 做设置
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    # 设置完成,将控制权传递给测试功能
    yield my_stuff
    # 做清理 
    my_stuff.finish()

到此为止,测试夹具简介!

有关更多信息,请参见官方py.test夹具文档和官方产量夹具文档。