pytest fixture-安全拆除

2022-03-18 14:12 更新

pytest的??fixture??系統(tǒng)非常強(qiáng)大,但它仍然是由計(jì)算機(jī)運(yùn)行的,所以它無法知道如何安全地拆除我們?nèi)咏o它的所有東西。如果我們不小心,錯(cuò)誤位置的錯(cuò)誤可能會留下測試遺留的內(nèi)容,這可能會很快導(dǎo)致進(jìn)一步的問題。

例如,考慮以下測試(基于上面的郵件示例):

# 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

這個(gè)版本更緊湊,但也更難閱讀,沒有一個(gè)非常描述性的??fixture??名稱,而且沒有一個(gè)??fixture??可以很容易地重用。

還有一個(gè)更嚴(yán)重的問題,即如果設(shè)置中的任何一個(gè)步驟引發(fā)異常,則所有的銷毀代碼都不會運(yùn)行。

一種選擇可能是使用??addfinalizer??方法,而不是??yield fixture??,但這可能會變得非常復(fù)雜和難以維護(hù)(而且它將不再緊湊)。

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

安全的fixture結(jié)構(gòu)

最安全、最簡單的??fixture??結(jié)構(gòu)要求限制每個(gè)??fixture??只做一個(gè)狀態(tài)更改操作,然后將它們與拆卸代碼捆綁在一起,如上面的電子郵件示例所示。

狀態(tài)更改操作失敗但仍然修改狀態(tài)的幾率可以忽略不計(jì),因?yàn)檫@些操作大多是基于事務(wù)的(至少在可能留下狀態(tài)的測試級別上)。因此,如果我們通過將任何成功的狀態(tài)更改操作移動到一個(gè)獨(dú)立的??fixture??函數(shù),并將其與其他可能失敗的狀態(tài)更改操作分開,從而確保任何成功的狀態(tài)更改操作都被刪除,那么我們的測試將最有可能以發(fā)現(xiàn)它的方式離開測試環(huán)境。

例如,假設(shè)我們有一個(gè)帶有登錄頁面的網(wǎng)站,我們可以訪問一個(gè)管理API,在那里我們可以生成用戶。對于我們的測試,我們想:

  1. 通過管理API創(chuàng)建一個(gè)用戶
  2. 使用Selenium啟動瀏覽器
  3. 進(jìn)入本站登錄頁面
  4. 以我們創(chuàng)建的用戶身份登錄
  5. 斷言他們的名字在登陸頁的標(biāo)題中

我們不想讓這個(gè)用戶留在系統(tǒng)中,也不想讓瀏覽器會話繼續(xù)運(yùn)行,所以我們希望確保創(chuàng)建這些東西的?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}!"

依賴項(xiàng)的布局方式意味著不清楚用戶??fixture??是否會在驅(qū)動程序??fixture??之前執(zhí)行。但這沒關(guān)系,因?yàn)檫@些都是原子操作,所以先運(yùn)行哪個(gè)并不重要因?yàn)闇y試的事件序列仍然是線性的。但真正重要的是,無論哪一個(gè)先運(yùn)行,如果其中一個(gè)引發(fā)異常而另一個(gè)沒有,那么兩者都不會留下任何東西。如果驅(qū)動程序在用戶之前執(zhí)行,并且用戶引發(fā)了異常,驅(qū)動程序仍然會退出,并且用戶從未被創(chuàng)建。如果驅(qū)動程序是引發(fā)異常的那個(gè),那么驅(qū)動程序?qū)⒂肋h(yuǎn)不會被啟動,用戶也永遠(yuǎn)不會被創(chuàng)建。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號