探索pytest的fixture(下)

這篇文章接著上一篇《探索pytest的fixture(上)》的內容講。

fixture的網絡圖片2

使用fixture函數的fixture

我們不僅可以在測試函數中使用fixture,而且fixture函數也可以使用其他fixture,這有助于fixture的模塊化設計,并允許在許多項目中重新使用框架特定的fixture。例如,我們可以擴展前面的例子,并實例化一個app對象,我們把已經定義好的smtp資源粘貼到其中,新建一個test_appsetup.py文件,輸入以下代碼:

import pytest

class App(object):
    def __init__(self, smtp):
        self.smtp = smtp

@pytest.fixture(scope="module")
def app(smtp):
    return App(smtp)

def test_smtp_exists(app):
    assert app.smtp

在這里,我們聲明一個app fixture,用來接收之前定義的smtp fixture,并用它實例化一個App對象,讓我們來運行它:

test_appsetup.py文件執行截圖

由于smtp的參數化,測試將運行兩次不同的App實例和各自的smtp服務器。pytest將完全分析fixture依賴關系圖,因此app fixture不需要知道smtp參數化。

還要注意一下的是,app fixture具有一個module(模塊)范圍,并使用module(模塊)范圍的smtp fixture。如果smtp被緩存在一個session(會話)范圍內,這個例子仍然可以工作,fixture使用更大范圍內的fixture是好的,但是不能反過來,session(會話)范圍的fixture不能以有意義的方式使用module(模塊)范圍的fixture。

通過fixture實例自動分組測試

pytest在測試運行期間會最小化活動fixture的數量,如果我們有一個參數化的fixture,那么所有使用它的測試將首先執行一個實例,然后在下一個fixture實例被創建之前調用終結器。除此之外,這可以簡化對創建和使用全局狀態的應用程序的測試。

以下示例使用兩個參數化fixture,其中一個作用于每個模塊,所有功能都執行print調用來顯示設置流程,修改之前的test_module.py文件,輸入以下代碼:

import pytest

@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
    param = request.param
    print ("  設置 modarg %s" % param)
    yield param
    print ("  拆卸 modarg %s" % param)

@pytest.fixture(scope="function", params=[1,2])
def otherarg(request):
    param = request.param
    print ("  設置 otherarg %s" % param)
    yield param
    print ("  拆卸 otherarg %s" % param)

def test_0(otherarg):
    print ("  用 otherarg %s 運行 test0" % otherarg)
def test_1(modarg):
    print ("  用 modarg %s 運行 test1" % modarg)
def test_2(otherarg, modarg):
    print ("  用 otherarg %s 和 modarg %s 運行 test2" % (otherarg, modarg))

讓我們使用pytest -v -s test_module.py運行詳細模式測試并查看打印輸出:

test_module.py文件執行截圖

我們可以看到參數化的module(模塊)范圍的modarg資源影響了測試執行的排序,使用了最少的活動資源。mod1參數化資源的終結器是在mod2資源建立之前執行的。特別要注意test_0是完全獨立的,會首先完成,然后用mod1執行test_1,再然后用mod1執行test_2,再然后用mod2執行test_1,最后用mod2執行test_2。otherarg參數化資源是function(函數)的范圍,是在每次使用測試之后建立起來的。

使用類、模塊或項目的fixture

有時測試函數不需要直接訪問一個fixture對象,例如,測試可能需要使用空目錄作為當前工作目錄,不關心具體目錄。這里使用標準的tempfile和pytest fixture來實現它,我們將fixture的創建分隔成一個conftest.py文件:

import pytest
import tempfile
import os

@pytest.fixture()
def cleandir():
    newpath = tempfile.mkdtemp()
    os.chdir(newpath)

并通過usefixtures標記聲明在測試模塊中的使用,新建一個test_setenv.py文件,輸入以下代碼:

import os
import pytest

@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit(object):
    def test_cwd_starts_empty(self):
        assert os.listdir(os.getcwd()) == []
        with open("myfile", "w") as f:
            f.write("hello")

    def test_cwd_again_starts_empty(self):
        assert os.listdir(os.getcwd()) == []

由于使用了usefixtures標記,所以執行每個測試方法都需要cleandir fixture,就像為每個測試方法指定了一個cleandir函數參數一樣,讓我們運行它來驗證我們的fixture已激活:

test_setenv.py文件執行截圖

我們可以像這樣指定多個fixture:

@pytest.mark.usefixtures("cleandir", "anotherfixture")

我們可以使用標記機制的通用功能來指定測試模塊級別的fixture使用情況:

pytestmark = pytest.mark.usefixtures("cleandir")

要注意的是,分配的變量必須被稱為pytestmark,例如,分配foomark不會激活fixture。最后,我們可以將項目中所有測試所需的fixture放入一個pytest.ini文件中:

[pytest]
usefixtures = cleandir

自動使用fixture

有時候,我們可能希望自動調用fixture,而不是顯式聲明函數參數或使用usefixtures裝飾器,例如,我們有一個數據庫fixture,它有一個開始、回滾、提交的體系結構,我們希望通過一個事務和一個回滾自動地包含每一個測試方法,新建一個test_db_transact.py文件,輸入以下代碼:

import pytest

class DB(object):
    def __init__(self):
        self.intransaction = []
    def begin(self, name):
        self.intransaction.append(name)
    def rollback(self):
        self.intransaction.pop()

@pytest.fixture(scope="module")
def db():
    return DB()

class TestClass(object):
    @pytest.fixture(autouse=True)
    def transact(self, request, db):
        db.begin(request.function.__name__)
        yield
        db.rollback()

    def test_method1(self, db):
        assert db.intransaction == ["test_method1"]

    def test_method2(self, db):
        assert db.intransaction == ["test_method2"]

類級別的transact fixture被標記為autouse=true,這意味著類中的所有測試方法將使用該fixture,而不需要在測試函數簽名或類級別的usefixtures裝飾器中陳述它。如果我們運行它,會得到兩個通過的測試:

test_db_transact.py文件執行截圖

以下是在其他范圍內如何使用自動fixture:

  • 自動fixture遵循scope=關鍵字參數,如果一個自動fixture的scope='session',它將只運行一次,不管它在哪里定義。scope='class'表示每個類會運行一次,等等。
  • 如果在一個測試模塊中定義一個自動fixture,所有的測試函數都會自動使用它。
  • 如果在conftest.py文件中定義了自動fixture,那么在其目錄下的所有測試模塊中的所有測試都將調用fixture。
  • 最后,要小心使用自動fixture,如果我們在插件中定義了一個自動fixture,它將在插件安裝的所有項目中的所有測試中被調用。例如,在pytest.ini文件中,這樣一個全局性的fixture應該真的應該做任何工作,避免無用的導入或計算。

最后還要注意,上面的的transact fixture可能是我們希望在項目中提供的fixture,而沒有通常的激活,規范的方法是將定義放在conftest.py文件而不使用自動運行:

@pytest.fixture
def transact(request, db):
    db.begin()
    yield
    db.rollback()

然后例如,有一個測試類通過聲明使用它需要:

@pytest.mark.usefixtures("transact")
class TestClass(object):
    def test_method1(self):
        ......

在這個測試類中的所有測試方法將使用transact fixture,而模塊中的其他測試類或函數將不會使用它,除非它們也添加一個transact引用。

覆蓋不同級別的fixture

在相對較大的測試套件中,我們很可能需要使用本地定義的套件重寫全局或根fixture,從而保持測試代碼的可讀性和可維護性。

覆蓋文件夾級別的fixture

鑒于測試文件的結構是:

tests/
    __init__.py

    conftest.py
        # tests/conftest.py
        import pytest

        @pytest.fixture
        def username():
            return 'username'

    test_something.py
        # tests/test_something.py
        def test_username(username):
            assert username == 'username'

    subfolder/
        __init__.py

        conftest.py
            # tests/subfolder/conftest.py
            import pytest

            @pytest.fixture
            def username(username):
                return 'overridden-' + username

        test_something.py
            # tests/subfolder/test_something.py
            def test_username(username):
                assert username == 'overridden-username'

正如上面代碼所示,具有相同名稱的fixture可以在某些測試文件夾級別上被覆蓋,但是要注意的是,basesuper fixture可以輕松地從上面的fixture進入,并在上面的例子中使用。

覆蓋測試模塊級別的fixture

鑒于測試文件的結構是:

tests/
    __init__.py

    conftest.py
        # tests/conftest.py
        @pytest.fixture
        def username():
            return 'username'

    test_something.py
        # tests/test_something.py
        import pytest

        @pytest.fixture
        def username(username):
            return 'overridden-' + username

        def test_username(username):
            assert username == 'overridden-username'

    test_something_else.py
        # tests/test_something_else.py
        import pytest

        @pytest.fixture
        def username(username):
            return 'overridden-else-' + username

        def test_username(username):
            assert username == 'overridden-else-username'

在上面的例子中,某個測試模塊可以覆蓋同名的fixture。

直接用測試參數化覆蓋fixture

鑒于測試文件的結構是:

tests/
    __init__.py

    conftest.py
        # tests/conftest.py
        import pytest

        @pytest.fixture
        def username():
            return 'username'

        @pytest.fixture
        def other_username(username):
            return 'other-' + username

    test_something.py
        # tests/test_something.py
        import pytest

        @pytest.mark.parametrize('username', ['directly-overridden-username'])
        def test_username(username):
            assert username == 'directly-overridden-username'

        @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
        def test_username_other(other_username):
            assert other_username == 'other-directly-overridden-username-other'

在上面的例子中,fixture值被測試參數值覆蓋,即使測試不直接使用它,fixture的值也可以用這種方式重寫。

使用非參數化參數替代參數化的fixture

鑒于測試文件的結構是:

tests/
    __init__.py

    conftest.py
        # tests/conftest.py
        import pytest

        @pytest.fixture(params=['one', 'two', 'three'])
        def parametrized_username(request):
            return request.param

        @pytest.fixture
        def non_parametrized_username(request):
            return 'username'

    test_something.py
        # tests/test_something.py
        import pytest

        @pytest.fixture
        def parametrized_username():
            return 'overridden-username'

        @pytest.fixture(params=['one', 'two', 'three'])
        def non_parametrized_username(request):
            return request.param

        def test_username(parametrized_username):
            assert parametrized_username == 'overridden-username'

        def test_parametrized_username(non_parametrized_username):
            assert non_parametrized_username in ['one', 'two', 'three']

    test_something_else.py
        # tests/test_something_else.py
        def test_username(parametrized_username):
            assert parametrized_username in ['one', 'two', 'three']

        def test_username(non_parametrized_username):
            assert non_parametrized_username == 'username'

在上面的例子中,一個參數化的fixture被一個非參數化的版本覆蓋,一個非參數化的fixture被某個測試模塊的參數化版本覆蓋,這同樣適用于測試文件夾級別。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容