1. fixture的作用域
1.1 scope
通過前面文章的介紹,我們知道@pytest.fixture()有一個scope參數,并且有五個作用域級別,這五個作用域級別的說明總結如下:
注意:fixture是在測試首次請求時創建的,并基于它們的作用域被銷毀。
scope | 說明 |
---|---|
function | 默認值,函數或方法級別被調用,當函數執行結束后,fixture則被銷毀 |
class | 類級別調用一次,當類中的最后一個測試函數執行結束后,fixture則被銷毀 |
module | 模塊級別(每一個.py文件)調用一次,當模塊中最后一個測試函數執行結束后,fixture則被銷毀 |
package | 包(一個文件夾下的.py文件)級別調用一次,當包中最后一個測試函數執行結束后,fixture則被銷毀 |
session | 多個文件調用一次,當多文件中最后一個測試函數執行結束后,fixture則被銷毀 |
單看說明,還是不太好理解的,所以下面我們通過示例來了解一下scope的用法。
示例1:
1.function:
演示代碼:
import pytest @pytest.fixture() def login(): print('登陸操作') yield print('注銷操作') class TestClass: def test_case1(self,login): print("TestClass:test_case1") def test_case2(self): print("TestClass:test_case2")
運行結果:
說明:
演示代碼中,我們定義了一個fixture函數――login
和兩個測試函數 test_case1
和test_case2
。
裝飾器@pytest.fixture()
沒有指定scope
參數,因此使用默認值:function
。
test_case1
和test_case2
在TestClass
類中,且test_case1
調用了login
,test_case2
未調用。
通過運行結果,我們可以看出來,scope=function
的fixture――login
,只在調用了它的test_case1
這個測試函數前后執行。test_case2
未執行。
2.class:
演示代碼:
import pytest @pytest.fixture(scope='class') #名為login的fixture,完成登陸和注冊操作 def login(): print('登陸操作') yield print('注銷操作') class TestClass1: def test_case1(self,login): #調用了login print("TestClass1:test_case1") def test_case2(self): print("TestClass1:test_case2") class TestClass2: def test_case1(self): print("TestClass2:test_case1") def test_case2(self,login): #調用了login print("TestClass2:test_case2") @pytest.mark.usefixtures("login") #TestClass3這個類,使用usefixtures調用了login class TestClass3: def test_case1(self): print("TestClass3:test_case1") def test_case2(self): print("TestClass3:test_case2")
運行結果:
說明:
演示代碼中我們定義了一個fixture――login
;三個測試類――TestClass1
、TestClass2
和TestClass3
,這個三個測試類中都定了兩個測試函數――test_case1
和test_case2
。
對fixture函數我們聲明了其作用域――scope='class'
。
TestClass1
中,只有test_case1
調用了login
;
TestClass2
中,只有test_case1
調用了login
;
TestClass3
,我們使用@pytest.mark.usefixtures("login")
,讓整個類調用了login
。
查看運行結果我們可以發現:
TestClass1
中,test_case1
運行前有”登陸操作“打印,并在test_case2
執行后有”注銷操作“顯打印。
TestClass2
中,只有test_case2
運行前有”登陸操作“打印,并在test_case2
執行后有”注銷操作“打印。而test_case1
執行前后并無任何數據打印。
TestClass3
則與Testclass1
執行后數據顯示一致。
TestClass1
和TestClass2
結果的不同是由什么原因造成的呢?
官方文檔在介紹fixture的scope的時候,有這樣一句話,上文中也有提到:fixture是在測試首次請求時創建的,并基于它們的作用域被銷毀。(Fixtures are created when first requested by a test, and are destroyed based on their scope。)
對于TestClass1
,test_case1
調用了login
,因此會在test_case1
執行的時候,創建login
,并在其執行之前打印了”登陸操作“。又根據login
的scope――class
,在TestClass1
的最后一個測試函數test_case2
執行后,根據login
的功能打印”注銷操作“,并銷毀login
。
對于TestClass2
,login
是由test_case2
調用的,而test_case1
比test_case2
先執行,所以test_case1
執行的時候login
還未被調用,fixture未被創建。直到執行test_case2
時,login
才被創建并進行了對應的操作。
后面其他類型的作用域示例中將不會在演示此種情況。
3.module:
演示代碼:
import pytest @pytest.fixture(scope='module') #名為login的fixture,完成登陸和注冊操作 def login(): print('登陸操作') yield print('注銷操作') @pytest.mark.usefixtures("login") class TestClass1: def test_case1(self): print("TestClass1:test_case1") def test_case2(self): print("TestClass1:test_case2") class TestClass2: def test_case1(self): print("TestClass2:test_case1") def test_case2(self): print("TestClass2:test_case2")
運行結果:
說明:
演示代碼中,我們定義了:
一個fixture――login()
,并且聲明其scope=”module“
;
兩個測試類――TestClass1
和TestClass2
;
兩個測試類中分別定義了兩個測試函數――test_case1
和test_case2
;
TestClass1
使用@pytest.mark.usefixtures("login")
的方法,調用了login()
。
運行結果顯示:
執行時,自第一個測試函數TestClass1
中的test_case1
調用login()
時,該fixture被創建,并且按照login()
實現的功能打印"登陸操作",并在當前模塊中(一個.py文件)最后一個測試函數TestClass2
中的test_case2
執行結束后,按照login()
實現的功能打印"注銷操作",然后被銷毀。
4.package:
演示代碼:
首先看一下測試包目錄,新增一個conftest.py
。里面所有的模塊都是上面的演示代碼。修改這些模塊里代碼,注釋調feature函數,刪掉調用即可。
conftest.py
import pytest @pytest.fixture(scope='package',autouse=True) #名為login的fixture,完成登陸和注冊操作 def login(): print('登陸操作') yield print('注銷操作')
使用命令行運行:
運行結果:
說明:
conftest.py中聲明了一個fixture――login()
,并且設置scope='package'
,autouse=True
。
使用命令行方式運行test_scope
這個測試包中的所有模塊。查看運行結果,在test_scope
中第一個測試函數調用login()
的時候,login創建,并按照功能打印”登陸操作“。在test_scope
中最后一個測試函數運行結束后,按照login()
功能打印出”注銷操作“,并銷毀login()
。
5.session:
使用package的演示代碼,需改conftest.py
中login()
的scope='session'
,然后我們直接在終端中使用命令行運行多個文件。
運行以下命令:
運行結果:
說明:
使用命令行方式運行test_scope
這個測試包中的test_class.py和test_function.py。查看運行結果,在test_class.py
中第一個測試函數調用login()
的時候,login創建,并按照功能打印”登陸操作“。在test_function.py
中最后一個測試函數運行結束后,按照login()
功能打印出”注銷操作“,并銷毀login()
1.2 動態作用域(Dynamic scope)
在某些情況下,我希望在不更改代碼的情況下更改feature的作用域。pytest的從5.2版本開始,提供了動態作用域的方法,來解決這種情況。
通過官方文檔的說明,我們了解到,可以將一個可調用對象傳遞給scope來實現動態作用域。這個可調用對象必須滿足以下三個條件:
1.這個可調用對象返回類型需是string
,且必須是有效的scope級別,即只能返回"function"
、"class"
、"module"
、"package"
、"session"
中的一個。
2.在fixture定義期間,這個可調用對象只能被執行一次,不能被多次調用。
3.這個可調用對象定義的時候,必須使用fixture_name
和config
作為參數。
下面通過示例給大家演示以下用法。
示例2:
演示代碼:
import pytest def dynamic_fixture_scope(fixture_name,config): if config.getoption("-k",None): return "function" return "class" @pytest.fixture(scope=dynamic_fixture_scope,autouse=True) def login(): print('登陸操作') yield print('注銷操作') class TestClass1: def test_A(self): print("這是TestClass1:test_A") def test_B(self): print("這是TestClass1:test_B") class TestClass2: def test_A(self): print("這是TestClass2:test_A")
說明:
dynamic_fixture_scope
:用來管理fixture的作用域,這個函數按照pytest官方文檔的要求,帶有fixture_name
和config
兩個關鍵字作為參數,并且實現下述功能:
1.當用命令行運行測試時,運行命令中帶有‘-k'
參數,則被調的fixture函數的作用域為"function"
2.其余情況下,被調的fixture的函數為"class"
。
login
:一個使用動態作用域方法的fixture,其scope=dynamic_fixture_scope
。為了方便演示,autouse
也被設置為True
。
下面我們分兩種情況來運行看看:
1.帶參數-k運行
運行命令:我們只運行測試名稱中包含test_A
的測試函數。
pytest -vs -k 'test_A' test_dynamicscope.py
運行結果:
通過運行結果可以看到,當命令行中出現-k
參數的時候,login
的scope
確實是function
級別的。兩個被選中的測試函數分別在不同的測試類中,但是都調用了login
。
2.無參數-k運行
運行命令:我們不帶參數-k
運行。
pytest -vs test_dynamicscope.py
運行結果:
通過運行結果可以看到,無-k
參數的時候,login
的scope
確實是class
級別的。
2. fixture的實例化順序
根據官方文檔,我們了解到影響fixture實例化順序的三個因素是:
1. 作用域(scope)
2. 依賴項
3. 自動調用(autouse)
而fixture名稱、測試函數名稱以及定義它們的位置一般都不會影響執行順序。
下面介紹一下這個三個因素是如何影響實例化順序的。
2.1 作用域級別高的fixture先執行
我們直接使用官方文檔提供的示例來說明。
示例3:
演示代碼:
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"]
運行結果:
說明:
演示代碼中我們定義了:
六個fixture函數:
order
:scope為session級別,返回一個空list。func
: 調用了order,scope為默認的function級別,并實現向order返回的列表中插入”function“的操作。cls
:調用了order,scope為class級別,并實現向order返回的列表中插入”class“的操作。mod
:調用了order,scope為module級別,并實現向order返回的列表中插入”module“的操作。pack
:調用了order,scope為package級別,并實現向order返回的列表中插入”package“的操作。sess
:調用了order,scope為session級別,并實現向order返回的列表中插入”session“的操作。
一個測試函數:
test_order
:主要目的是通過list中元素的順序來判斷是否按照預想的順序執行fixture。
根據運行結果顯示,測試斷言通過,也就是fixture的執行順序是按照我們預期的scope的級別由高到低去執行的。
test_order
調用fixture的順序是func
, cls
, mod
, pack
, sess
, order
,而實際執行的順序是order
, sess
, pack
, mod
, cls
, func
。由此可見,我們在調用時定義的順序不會影響到fixture的實際執行順序的。
官網執行順序圖:
其中sess
與order
的scope都是session級別的,但是因為order
是sess的依賴項,所以會先調用order
,這個一點正好我們下面要說明。
2.2 fixture函數的依賴項先執行
前面文章中有說過,fixture函數是可以調用另外一個fixture函數的。在執行的時候,也是先執行調用的那個fixture函數。舉了例子,fixture a 調用了fixture b,當測試函數調用fixture a的時候,會先去執行fixture b 然后再執行fixture a,最后執行測試函數。
這是因為fixture b 是fixture a 的依賴項,fixture a 可能會需要fixture b提供一些條件才能正常被執行。
下面我們來看一下官方文檔提供的一個例子,
示例4:
演示代碼:
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函數和一個測試函數,其中:
fixture函數有:orde
、a
、b
、c
、d
、e
、f
、g
測試函數有:test_order
a
調用了order
;
b
調用了a
, order
;
c
調用了a
, b
, order
;
d
調用了c
, b
, order
;
f
調用了e
, order
;
g
調用了f
, c
, order
;
test_order
調用了g
、order
。
我們來整理以上所有函數的依賴關系,他們之間的依賴關系如圖所示:
之前文章也有說過,在同一次測試執行過程中,fixture是可以被多次調用的,但是不會被多次執行。執行一次后,fixture的實例對象和返回值會存在緩存中,下次再被調用的時候是直接從緩存中獲取的。
所以上面的順序我們可以再簡化一下:
執行完所有依賴的fixture函數后,我們得到的order的結果為:['a','b','c','d','e']
,因此測試斷言通過。
2.3 自動使用的fixture在其作用域內首先執行
根據官方文檔的說明,我們可以得到一下兩點重要內容:
1.當測試函數調用的fixture的作用域級別都一樣,那么會首先調用自動使用的fixture。
示例5:
import pytest @pytest.fixture def order(): return [] @pytest.fixture def func(order): order.append("function") @pytest.fixture(autouse=True) #修改cls為自動使用fixture def cls(order): order.append("class") @pytest.fixture def mod(order): order.append("module") @pytest.fixture def pack(order): order.append("package") @pytest.fixture def sess(order): order.append("session") class TestClass: def test_order(self, func, cls, mod, pack, sess, order): print(order) assert order == ["session", "package", "module", "class", "function"]
運行結果:
說明:
我們把示例3的代碼拿過來修改一下,把所有fixture的scope都改為function級別,并且對cls試著autouse=True。
test_order請求fixture的順序是:func, cls, mod, pack, sess
。
當scope級別一樣的時候,且無依賴關系的時候,fixture的執行順序應該與調用順序一致,也應該是func, cls, mod, pack, sess
。但是實際運行的結果卻是['class', 'function', 'module', 'package', 'session']
。由此可以判斷出,在scope一致且無依賴的情況下,自動執行的fixture是最先被執行的。
2.對一個autouse的fixture A來說,若調用了非autouse的fixture B,那么對于調用了fixture A的測試函數來說,fixture B也相當于是autouse的。
我們再來看一下官方給的一個示例。
示例6:
演示代碼:
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(autouse=True) def c(b, order): order.append("c") @pytest.fixture def d(b, order): order.append("d") @pytest.fixture def e(d, 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_and_g(g, order): assert order == ["a", "b", "c", "d", "e", "f", "g"]
說明:
演示代碼中定義了八個fixture函數和一個測試函數,其中:
fixture函數有:orde
、a
、b
、c
、d
、e
、f
、g
測試函數有:test_order
a
調用了order
;
b
調用了a
, order
;
c
調用了 b
, order
,且是自動使用fixture;
d
調用了 b
, order
;
f
調用了e
, order
;
g
調用了f
, c
, order
;
test_order
調用了g
、order
。
若c是非自動使用的fixture,代碼中fixture的一個執行順序是什么樣的呢?
下圖是運行結果:
根據上面的運行結果和下面的順序圖,我們可以看到,除了g調用了c之外,其他函數都沒有調用c,而g也調用了f,所以現在還不清楚c應該在d、e、f之前還是之后執行。對c來說他只需在b之后、g之前執行即可。而實際運行結果,c是在d、e、f之后和g之前執行的。
對于pytest來說,fixture的執行順序就是不明確,這種情況很可能會影響測試函數的執行行為,影響了測試結果。
所以,我們需要明確fixture的執行順序。
當我們使c為自動使用fixture時,這個順序又會發生什么變化呢?
下圖為運行結果:
根據上圖的運行結果和下圖的順序圖,我們可以看出來,當自動使用c后,它的執行優先級就比d要高,所以可以保證c是在d之前執行的。這樣使執行順序明確了。
當c是自動使用的fixture后,根據fixture函數的依賴項先執行這一規則,c所依賴的b、b所依賴的a和a所依賴的order都會被執行。這樣對于調用了c的g來說,order、a、b也相當于是自動使用的fixture了,但是當有其他fixture調用order、a、b時,order、a、b仍然保持自己的特性。
本節總結
我們來總結一下fixture實例化順序的一個規則:
1.fixture的scope級別越高,那么它執行的優先級越高。優先級為:session>package>module>class>function
2.fixture如果存在依賴項,那么它所依賴的fixture函數會先被執行。
3.同scope級別fixture中,自動使用的fixture會最先執行;若該fixture存在依賴項,則對于調用了fixture的測試函數來說,這些依賴項也可以看做是自動使用的。
4.我們在編碼的時候最好可以依照以上規則為pytest提供一個清晰的fixture執行順序,以免影響測試函數的行為和測試結果。
3. fixture的可用性
對于測試函數來說,能夠調用那些fixture,是由fixture定義的位置決定。
1.當fixture定義在一個測試類中時,只有該類中的測試函數可調用此fixture,其他測試函數無法調用該fixture。當測試函數在模塊的全局范圍內定,則模塊下的所有測試函數都可以調用它。該特點與全局變量和局部變量相似。
示例7:
演示代碼:
import pytest class TestClass1: @pytest.fixture() def login(self): print("login") def test_case1(self,login): print("TestClass1::test_case1") class TestClass2: def test_case2(self,login): print("test_case2")
運行結果:
說明:
演示代碼中定義了兩個測試類:TestClass1
和TestClass2
。
TestClass1
中定義了一個fixture――login
,和測試函數test_case1
,test_case1
有調用login
。
TestClass1
中定義了一個測試函數test_case2
,test_case2
有調用login
。
通過運行結果我們可以看到,與login
在同一測試類中的test_case1
成功調用了login
,但是TestClass2
中test_case2
調用login
的時候報錯了,報錯原因:未發現fixture函數“login”
。
2.conftest.py中定義的fixture,可以被同目錄下的所有模塊中定義的測試函數調用。
示例8:
演示代碼:
test_availability/conftest.py:
import pytest @pytest.fixture() def login(): print("login")
test_availability/test_availability.py:
class TestClass1: def test_case1(self,login): print("TestClass1::test_case1") class TestClass2: def test_case2(self,login): print("test_case2")
運行結果:
說明:
我們在test_availability目錄下創建了一個conftest.py,并且定義了一個fixture――login
。
在test_availability.py中,我們定義了兩個測試函數test_case1
和test_case1
,兩個測試函數都調用了login
。
查看運行結果,測試函數都成功調用了login
。
3.如果安裝的第三方插件提供了fixture,任何測試函數都可以正常使用,無需考慮位置因素。
文末說明:
以上內容是我在閱讀pytest官方文檔后,依照個人理解進行整理。內容可能會有理解錯誤之處,歡迎大家留言指正。謝謝
更多關于fixture作用域實例化順序及可用性的資料請關注服務器之家其它相關文章!
原文鏈接:https://blog.csdn.net/gjj920318/article/details/118704453