pytest fixture-安全拆除

2022-03-18 14:12 更新

pytest的​​fixture​​系统非常强大,但它仍然是由计算机运行的,所以它无法知道如何安全地拆除我们扔给它的所有东西。如果我们不小心,错误位置的错误可能会留下测试遗留的内容,这可能会很快导致进一步的问题。

例如,考虑以下测试(基于上面的邮件示例):

# content of test_emaillib.py
import pytest

from emaillib import Email, MailAdminClient


@pytest.fixture
def setup():
    mail_admin = MailAdminClient()
    sending_user = mail_admin.create_user()
    receiving_user = mail_admin.create_user()
    email = Email(subject="Hey!", body="How's it going?")
    sending_user.send_email(email, receiving_user)
    yield receiving_user, email
    receiving_user.clear_mailbox()
    mail_admin.delete_user(sending_user)
    mail_admin.delete_user(receiving_user)


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

这个版本更紧凑,但也更难阅读,没有一个非常描述性的​​fixture​​名称,而且没有一个​​fixture​​可以很容易地重用。

还有一个更严重的问题,即如果设置中的任何一个步骤引发异常,则所有的销毁代码都不会运行。

一种选择可能是使用​​addfinalizer​​方法,而不是​​yield fixture​​,但这可能会变得非常复杂和难以维护(而且它将不再紧凑)。

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

安全的fixture结构

最安全、最简单的​​fixture​​结构要求限制每个​​fixture​​只做一个状态更改操作,然后将它们与拆卸代码捆绑在一起,如上面的电子邮件示例所示。

状态更改操作失败但仍然修改状态的几率可以忽略不计,因为这些操作大多是基于事务的(至少在可能留下状态的测试级别上)。因此,如果我们通过将任何成功的状态更改操作移动到一个独立的​​fixture​​函数,并将其与其他可能失败的状态更改操作分开,从而确保任何成功的状态更改操作都被删除,那么我们的测试将最有可能以发现它的方式离开测试环境。

例如,假设我们有一个带有登录页面的网站,我们可以访问一个管理API,在那里我们可以生成用户。对于我们的测试,我们想:

  1. 通过管理API创建一个用户
  2. 使用Selenium启动浏览器
  3. 进入本站登录页面
  4. 以我们创建的用户身份登录
  5. 断言他们的名字在登陆页的标题中

我们不想让这个用户留在系统中,也不想让浏览器会话继续运行,所以我们希望确保创建这些东西的​fixture​在它们自己清理之后。

这可能是这样的:

from uuid import uuid4
from urllib.parse import urljoin

from selenium.webdriver import Chrome
import pytest

from src.utils.pages import LoginPage, LandingPage
from src.utils import AdminApiClient
from src.utils.data_types import User


@pytest.fixture
def admin_client(base_url, admin_credentials):
    return AdminApiClient(base_url, **admin_credentials)


@pytest.fixture
def user(admin_client):
    _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$word")
    admin_client.create_user(_user)
    yield _user
    admin_client.delete_user(_user)


@pytest.fixture
def driver():
    _driver = Chrome()
    yield _driver
    _driver.quit()


@pytest.fixture
def login(driver, base_url, user):
    driver.get(urljoin(base_url, "/login"))
    page = LoginPage(driver)
    page.login(user)


@pytest.fixture
def landing_page(driver, login):
    return LandingPage(driver)


def test_name_on_landing_page_after_login(landing_page, user):
    assert landing_page.header == f"Welcome, {user.name}!"

依赖项的布局方式意味着不清楚用户​​fixture​​是否会在驱动程序​​fixture​​之前执行。但这没关系,因为这些都是原子操作,所以先运行哪个并不重要因为测试的事件序列仍然是线性的。但真正重要的是,无论哪一个先运行,如果其中一个引发异常而另一个没有,那么两者都不会留下任何东西。如果驱动程序在用户之前执行,并且用户引发了异常,驱动程序仍然会退出,并且用户从未被创建。如果驱动程序是引发异常的那个,那么驱动程序将永远不会被启动,用户也永远不会被创建。


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

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号