pytest fixture-拆除fixture

2022-03-18 14:10 更新

在我们运行测试时,我们希望确保它们在自己完成之后进行清理,这样它们就不会扰乱其他测试(也不会留下大量的测试数据来膨胀系统)。pytest中的​​fixture​​提供了一个非常有用的拆卸系统,它允许我们为每个​​fixture​​定义必要的特定步骤,以便在它们自己之后进行清理。

该系统可以利用在两个方面。

1、yield fixtures

使用这些​​fixture​​,我们可以运行一些代码并将一个对象传回请求​​fixture/test​​,就像使用其他​​fixture​​一样。唯一的区别是:

  • ​​return​​被换成了​​yield​​。
  • 该​​fixture​​的拆卸代码位于生成之后。

一旦pytest为​​fixture​​确定了一个线性顺序,它将运行每个​​fixture​​,直到它返回或产生,然后移动到列表中的下一个​​fixture​​来做同样的事情。

测试完成后,pytest将返回​​fixture​​列表,但顺序相反,获取每个产生的​​fixture​​,并在其中运行​​yield​​语句之后的代码。

作为一个简单的例子,考虑这个基本的电子邮件模块:

# content of emaillib.py
class MailAdminClient:
    def create_user(self):
        return MailUser()

    def delete_user(self, user):
        # do some cleanup
        pass


class MailUser:
    def __init__(self):
        self.inbox = []

    def send_email(self, email, other):
        other.inbox.append(email)

    def clear_mailbox(self):
        self.inbox.clear()


class Email:
    def __init__(self, subject, body):
        self.subject = subject
        self.body = body

假设我们想测试从一个用户向另一个用户发送电子邮件。我们必须首先创建每个用户,然后从一个用户向另一个用户发送电子邮件,最后断言另一个用户在他们的收件箱中收到了这条消息。如果我们想在测试运行后进行清理,我们必须确保在删除其他用户之前清空该用户的邮箱,否则系统可能会报错。

这可能是这样的:

# content of test_emaillib.py
import pytest

from emaillib import Email, MailAdminClient


@pytest.fixture
def mail_admin():
    return MailAdminClient()


@pytest.fixture
def sending_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)


@pytest.fixture
def receiving_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)


def test_email_received(sending_user, receiving_user):
    email = Email(subject="Hey!", body="How's it going?")
    sending_user.send_email(email, receiving_user)
    assert email in receiving_user.inbox

因为​​receiving_user​​是安装期间运行的最后一个​​fixture​​,所以它是拆卸期间运行的第一个​​fixture​​。

$ pytest -q test_emaillib.py
.                                                                    [100%]
1 passed in 0.12s

处理yield fixture的错误

如果​​yield fixture​​在​​yield​​之前引发异常,pytest将不会尝试在该​​yield fixture​​的​​yield​​语句之后运行拆卸代码。但是,对于已经为该测试成功运行的每个​​fixture​​, pytest仍然会像正常情况一样试图将它们删除。

2、直接添加finalizers

虽然​​yield fixture​​被认为是更干净和更直接的选项,但还有另一种选择,即直接向测试的请求上下文对象添加​​finalizer​​函数。它带来了与​​yield fixture​​类似的结果,但需要更多的细节。为了使用这种方法,我们必须在需要添加​​teardown​​代码的​​fixture​​中请求请求上下文对象(就像我们请求另一个​​fixture​​一样),然后将包含该​​teardown​​代码的可调用对象传递给它的​​addfinalizer​​方法。但是,我们必须小心,因为pytest将在添加​​finalizer​​后运行该​​finalizer​​,即使该​​fixture​​在添加​​finalizer​​后引发异常。因此,为了确保我们不会在不需要的时候运行​​finalizer​​代码,我们只会在​​fixture​​做了一些我们需要拆除的事情时添加​​finalizer​​。下面是使用​​addfinalizer​​方法的前一个例子:

# content of test_emaillib.py
import pytest

from emaillib import Email, MailAdminClient


@pytest.fixture
def mail_admin():
    return MailAdminClient()


@pytest.fixture
def sending_user(mail_admin):
    user = mail_admin.create_user()
    yield user
    mail_admin.delete_user(user)


@pytest.fixture
def receiving_user(mail_admin, request):
    user = mail_admin.create_user()

    def delete_user():
        mail_admin.delete_user(user)

    request.addfinalizer(delete_user)
    return user


@pytest.fixture
def email(sending_user, receiving_user, request):
    _email = Email(subject="Hey!", body="How's it going?")
    sending_user.send_email(_email, receiving_user)

    def empty_mailbox():
        receiving_user.clear_mailbox()

    request.addfinalizer(empty_mailbox)
    return _email


def test_email_received(receiving_user, email):
    assert email in receiving_user.inbox

它比​​yield fixture​​要长一点,也更复杂一点,但当你在紧要关头时,它确实提供了一些细微的差别。

$ pytest -q test_emaillib.py
.                                                                    [100%]
1 passed in 0.12s


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号