天天看點

fixture的執行順序,3要素詳解

當pytest要執行一個測試函數,這個測試函數還請求了fixture函數,那麼這時候pytest就要先确定fixture的執行順序了。

影響因素有三:

  • ​scope​

    ​,就是fixture函數的作用範圍,比如​

    ​scope='class'​

    ​。
  • ​dependencies​

    ​,可能會存在fixture請求了别的fixture,是以産生了依賴關系,也要考慮進去。
  • ​autouse​

    ​,如果​

    ​autouse=True​

    ​,那麼在作用範圍内,這個fixture是最先調用的。

一、使用範圍更大的fixture函數優先執行

更大範圍(比如session)的fixture會在小範圍(比如函數或類)之前執行。

import pytest


@pytest.fixture(scope="session")
def order():
    return []


@pytest.fixture
def func(order):
    order.append("function")


@pytest.fixture(scope="class")
def cls(order):
    order.append("class")


@pytest.fixture(scope="module")
def mod(order):
    order.append("module")


@pytest.fixture(scope="package")
def pack(order):
    order.append("package")


@pytest.fixture(scope="session")
def sess(order):
    order.append("session")


class TestClass:
    def test_order(self, func, cls, mod, pack, sess, order):
        assert order == ["session", "package", "module", "class", "function"]      

運作結果:

test_module1.py .                                                        [100%]

============================== 1 passed in 0.01s ==============================
Process finished with exit code 0      

既然運作通過,那麼這些fixture函數的運作順序就是清單裡的順序​

​["session", "package", "module", "class", "function"]​

​。

二、相同順序的fixture基于依賴項執行

當一個fixture函數請另一個fixture函數,另一個會先執行。

比如,fixture​

​a​

​請求fixture​

​b​

​,需要用b傳回的結果。那麼b先執行,因為a依賴于b,必須得讓b先執行,否則a就沒法幹活,通過請求b來控制順序。

1.請求依賴呈線性情況下
import pytest


@pytest.fixture
def order():
    return []


@pytest.fixture
def a(order):
    order.append("a")


@pytest.fixture
def b(a, order):
    order.append("b")


@pytest.fixture
def c(a, b, order):
    order.append("c")


@pytest.fixture
def d(c, b, order):
    order.append("d")


@pytest.fixture
def e(d, b, order):
    order.append("e")


@pytest.fixture
def f(e, order):
    order.append("f")


@pytest.fixture
def g(f, c, order):
    order.append("g")


def test_order(g, order):
    assert order == ["a", "b", "c", "d", "e", "f", "g"]      

官方給出了上述代碼的依賴關系圖(左)和執行順序圖(右)。

fixture的執行順序,3要素詳解

不要方,隻要從測試函數​

​test_order​

​開始,一層一層跟着fixture的依賴梳理下去就對上了。到這裡,其實也就能更進一步了解了,如果想控制好執行順序,就要給這些請求依賴提供足夠的資訊。

這樣pytest能夠找出一個清晰的線性依賴鍊,最終給調用它們的測試函數一個确定的操作順序。

2.請求依賴不呈線性的情況,會影響操作執行

此外,如果存在歧義,出現多種執行順序,那pytest可以在多種順序裡任選。

基于上述的請求依賴關系圖(左),假設d沒有請求c,那麼此時的依賴關系就變成了右圖所示:

fixture的執行順序,3要素詳解

這時候,pytest就會認定,c可以在g和b之間的任何位置點執行。也就是說,有多種執行順序的選擇。

運作下代碼:

test_module1.py 
運作order
運作a
運作b
運作d
運作e
運作f
運作c
運作g
F
demo\test_module1.py:51 (test_order)
['a', 'b', 'd...'f', 'c', ...] != ['a', 'b', 'c...'e', 'f', ...]

Expected :['a', 'b', 'c...'e', 'f', ...]
Actual   :['a', 'b', 'd...'f', 'c', ...]      

會看到測試失敗了,因為fixture的執行順序變了,導緻添加到order清單的元素順序也變了,實際與預期結果不相等,測試失敗。不過可以從列印出的fixture運作順序看出,c确實在b之後和g之前運作了。

官方描述這些想要表達的什麼呢?

我覺得應該是這個,如果你希望精确控制執行順序,避免順序不對而造成執行操作或測試結果有誤,那麼就要給足請求依賴,好讓pytest線性制定執行順序。

三、Autouse的fixtures,會優先執行

1. autouse的妙用

如果請求了一個​

​autouse=True​

​的fixture函數,那麼這個autouse的fixture函數會比請求的其他fixture都要先執行。

另外,如果fixture a是autouse的,而fixture b不是。而fixture a又請求fixture b,那麼fixture b也将變成autouse的fixture

其實這點也很好了解,既然a是要先執行的,a又請求了b,說明a依賴于b,那麼b自然也是要先于a執行的。

在上一個例子中,由于d沒有去請求c,導緻依賴關系模糊,最後影響了執行結果。

但是如果c是autouse,那麼b和a也就自動變成了autouse,因為c依賴于b和a。是以,這時候,c,b,a都會在其他非autouse的fixture函數之前執行。

修改下代碼,在c上加上autouse:

import pytest


@pytest.fixture
def order():
    print("\n運作order")
    return []


@pytest.fixture
def a(order):
    print("運作a")
    order.append("a")


@pytest.fixture
def b(a, order):
    print("運作b")
    order.append("b")


@pytest.fixture(autouse=True)
def c(a, b, order):
    print("運作c")
    order.append("c")


@pytest.fixture
def d(b, order):
    print("運作d")
    order.append("d")


@pytest.fixture
def e(d, b, order):
    print("運作e")
    order.append("e")


@pytest.fixture
def f(e, order):
    print("運作f")
    order.append("f")


@pytest.fixture
def g(f, c, order):
    print("運作g")
    order.append("g")


def test_order(g, order):
    assert order == ["a", "b", "c", "d", "e", "f", "g"]      

運作結果:

test_module1.py 
運作order
運作a
運作b
運作c
運作d
運作e
運作f
運作g
.                                                        [100%]

============================== 1 passed in 0.01s ==============================
Process finished with exit code 0      

執行順序正常了,從a到g。

它們的依賴關系圖變成了這樣

fixture的執行順序,3要素詳解

因為c變成了autouse,是以在圖裡處于d之上的位置,這時候pytest又可以将執行順序線性化了。而且,c也讓b和a都變成了autouse的fixture。

2. autouse的慎用

在使用autouse的時候也要小心,因為一個測試函數即使沒有直接請求一個autouse fixture,但是隻要這個測試函數在這個autouse的作用範圍内,那麼這個autouse就會自動執行。

import pytest


@pytest.fixture(scope="class")
def order():
    return []


@pytest.fixture(scope="class", autouse=True)
def c1(order):
    order.append("c1")


@pytest.fixture(scope="class")
def c2(order):
    order.append("c2")


@pytest.fixture(scope="class")
def c3(order, c1):
    order.append("c3")


class TestClassWithC1Request:
    def test_order(self, order, c1, c3):
        assert order == ["c1", "c3"]


class TestClassWithoutC1Request:
    def test_order(self, order, c2):
        assert order == ["c1", "c2"]