當pytest要執行一個測試函式,這個測試函式還請求了fixture函式,那麼這時候pytest就要先確定fixture的執行順序了。
影響因素有三:
scope
,就是fixture函式的作用範圍,比如scope='class'
。dependencies
,可能會存在fixture請求了別的fixture,所以產生了依賴關係,也要考慮進去。autouse
,如果autouse=True
,那麼在作用範圍內,這個fixture是最先呼叫的。
所以,像fixture函式或測試函式的名稱、定義的位置、定義的順序以及請求fixture的順序,除了巧合之外,對執行順序沒有任何影響。
對於這些巧合情況,雖然pytest會盡力保持每次執行的順序都一樣,但是也難免會有意外。所以,如果我們想控制好順序,最安全的方法還是
依賴上述三點,並且要弄清依賴關係。
一、使用範圍更大的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函式,另一個會先執行。
比如,fixturea
請求fixtureb
,需要用b返回的結果。那麼b先執行,因為a依賴於b,必須得讓b先執行,否則a就沒法幹活。
另外,即使a不需要用b返回的結果,只要a需要確保在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"]
官方給出了上述程式碼的依賴關係圖(左)和執行順序圖(右)。
不要方,只要從測試函式test_order
開始,一層一層跟著fixture的依賴一層一層梳理下去就對上了。
到這裡,其實也就能更進一步理解了,如果想控制好執行順序,就要給這些請求依賴提供足夠的資訊。
這樣pytest能夠找出一個清晰的線性依賴鏈,最終給呼叫它們的測試函式一個確定的操作順序。
2.請求依賴不呈線性的情況,會影響操作執行
此外,如果存在歧義,出現多種執行順序,那pytest可以在多種順序裡任選。
基於上述的請求依賴關係圖(左),假設d沒有請求c,那麼此時的依賴關係就變成了右圖所示:
可以看出:
- c此時只被一個g請求。
- g既請求了c,還請求了f。
因為c現在除了一個g,其他沒有別的依賴關係,所以現在pytest不知道c應該是在f,e之前執行,還是應該在d之後執行。
這時候,pytest就會認定,c可以在g和b之間的任何位置點執行。也就是說,c必須在b之後和g之前執行。
如果這種情況出現,那麼你預期的測試行為或者測試結果可能會受到影響。可以修改下程式碼,讓d中沒有請求c,並且我加了print,方便
看fixture的執行順序。
執行下程式碼:
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是要先執行的,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。
它們的依賴關係圖變成了這樣:
因為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"]
執行程式碼,執行case是通過的,說明order == ["c1", "c2"]
。
可以看到,雖然類TestClassWithoutC1Request
(官方寫的是TestClassWithC1Request,應該是錯了)沒有請求c1
,但是c1
還是在這個類裡執行了。
但是,僅僅是一個autouse的fixture請求了一個非autouse的話,其實這並不能說這個非autouse的fixture也成為了一個可以應用到上下文的fixture函式。
僅僅是適用於請求它的那個autouse fixture的作用範圍。
例如,看下面程式碼:
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def c1(order):
order.append("c1")
@pytest.fixture
def c2(order):
order.append("c2")
class TestClassWithAutouse:
@pytest.fixture(autouse=True)
def c3(self, order, c2):
order.append("c3")
def test_req(self, order, c1):
assert order == ["c2", "c3", "c1"]
def test_no_req(self, order):
assert order == ["c2", "c3"]
class TestClassWithoutAutouse:
def test_req(self, order, c1):
assert order == ["c1"]
def test_no_req(self, order):
assert order == []
if __name__ == '__main__':
pytest.main(['-s', 'test_module2.py'])
執行都是可以通過的,這裡的依賴關係圖是這樣的。
在類TestClassWithAutouse
中:
test_req
和test_no_req
是2個測試方法。c3
是autouse,並且請求了c2
,所以c2
也成了autouse。雖然2個測試並沒去請求c2
和c3
,但是都執行了,而且在c1
之前執行。
在這裡,c3請求了還order,同樣地,在c3的作用域內,order也扮演了autouse的存在。但是在TestClassWithoutAutouse
,order就不是
autouse了,所以類TestClassWithoutAutouse
中的test_no_req
可以執行成功,因為order=[]
。