pytest fixture-拆除fixture
在我们运行测试时,我们希望确保它们在自己完成之后进行清理,这样它们就不会扰乱其他测试(也不会留下大量的测试数据来膨胀系统)。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
更多建议: