pytest自動化測試fixture的作用域?qū)嵗樞蚣翱捎眯?/h1>
更新時間:2021年10月09日 14:43:19 作者:愛測試的高胖胖
這篇文章主要介紹了pytest自動化測試中fixture的作用域、實例化順序及可用性的詳解示例有需要的朋友可以借鑒參考下,希望能夠有所幫助
1. fixture的作用域
1.1 scope
通過前面文章的介紹,我們知道@pytest.fixture()有一個scope參數(shù),并且有五個作用域級別,這五個作用域級別的說明總結(jié)如下:
注意:fixture是在測試首次請求時創(chuàng)建的,并基于它們的作用域被銷毀。
scope
說明
function
默認值,函數(shù)或方法級別被調(diào)用,當函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀
class
類級別調(diào)用一次,當類中的最后一個測試函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀
module
模塊級別(每一個.py文件)調(diào)用一次,當模塊中最后一個測試函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀
package
包(一個文件夾下的.py文件)級別調(diào)用一次,當包中最后一個測試函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀
session
多個文件調(diào)用一次,當多文件中最后一個測試函數(shù)執(zhí)行結(jié)束后,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")
運行結(jié)果:

說明:
演示代碼中,我們定義了一個fixture函數(shù)——login 和兩個測試函數(shù) test_case1 和test_case2。
裝飾器@pytest.fixture()沒有指定scope參數(shù),因此使用默認值:function。
test_case1 和test_case2在TestClass類中,且test_case1調(diào)用了login,test_case2未調(diào)用。
通過運行結(jié)果,我們可以看出來,scope=function的fixture——login,只在調(diào)用了它的test_case1這個測試函數(shù)前后執(zhí)行。test_case2未執(zhí)行。
2.class:
演示代碼:
import pytest
@pytest.fixture(scope='class') #名為login的fixture,完成登陸和注冊操作
def login():
print('登陸操作')
yield
print('注銷操作')
class TestClass1:
def test_case1(self,login): #調(diào)用了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): #調(diào)用了login
print("TestClass2:test_case2")
@pytest.mark.usefixtures("login") #TestClass3這個類,使用usefixtures調(diào)用了login
class TestClass3:
def test_case1(self):
print("TestClass3:test_case1")
def test_case2(self):
print("TestClass3:test_case2")
運行結(jié)果:

說明:
演示代碼中我們定義了一個fixture——login;三個測試類——TestClass1、TestClass2和TestClass3,這個三個測試類中都定了兩個測試函數(shù)——test_case1和test_case2。
對fixture函數(shù)我們聲明了其作用域——scope='class'。
TestClass1中,只有test_case1調(diào)用了login;
TestClass2中,只有test_case1調(diào)用了login;
TestClass3,我們使用@pytest.mark.usefixtures("login"),讓整個類調(diào)用了login。
查看運行結(jié)果我們可以發(fā)現(xiàn):
TestClass1中,test_case1運行前有”登陸操作“打印,并在test_case2執(zhí)行后有”注銷操作“顯打印。
TestClass2中,只有test_case2運行前有”登陸操作“打印,并在test_case2執(zhí)行后有”注銷操作“打印。而test_case1執(zhí)行前后并無任何數(shù)據(jù)打印。
TestClass3則與Testclass1執(zhí)行后數(shù)據(jù)顯示一致。
TestClass1和TestClass2結(jié)果的不同是由什么原因造成的呢?
官方文檔在介紹fixture的scope的時候,有這樣一句話,上文中也有提到:fixture是在測試首次請求時創(chuàng)建的,并基于它們的作用域被銷毀。(Fixtures are created when first requested by a test, and are destroyed based on their scope。)
對于TestClass1,test_case1調(diào)用了login,因此會在test_case1執(zhí)行的時候,創(chuàng)建login,并在其執(zhí)行之前打印了”登陸操作“。又根據(jù)login的scope——class,在TestClass1的最后一個測試函數(shù)test_case2執(zhí)行后,根據(jù)login的功能打印”注銷操作“,并銷毀login。
對于TestClass2,login是由test_case2調(diào)用的,而test_case1比test_case2先執(zhí)行,所以test_case1執(zhí)行的時候login還未被調(diào)用,fixture未被創(chuàng)建。直到執(zhí)行test_case2時,login才被創(chuàng)建并進行了對應(yīng)的操作。
后面其他類型的作用域示例中將不會在演示此種情況。
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")
運行結(jié)果:

說明:
演示代碼中,我們定義了:
一個fixture——login(),并且聲明其scope=”module“;
兩個測試類——TestClass1和TestClass2;
兩個測試類中分別定義了兩個測試函數(shù)——test_case1和test_case2;
TestClass1使用@pytest.mark.usefixtures("login")的方法,調(diào)用了login()。
運行結(jié)果顯示:
執(zhí)行時,自第一個測試函數(shù)TestClass1中的test_case1調(diào)用login()時,該fixture被創(chuàng)建,并且按照login()實現(xiàn)的功能打印"登陸操作",并在當前模塊中(一個.py文件)最后一個測試函數(shù)TestClass2中的test_case2執(zhí)行結(jié)束后,按照login()實現(xiàn)的功能打印"注銷操作",然后被銷毀。
4.package:
演示代碼:
首先看一下測試包目錄,新增一個conftest.py。里面所有的模塊都是上面的演示代碼。修改這些模塊里代碼,注釋調(diào)feature函數(shù),刪掉調(diào)用即可。

conftest.py
import pytest
@pytest.fixture(scope='package',autouse=True) #名為login的fixture,完成登陸和注冊操作
def login():
print('登陸操作')
yield
print('注銷操作')
使用命令行運行:

運行結(jié)果:

說明:
conftest.py中聲明了一個fixture——login(),并且設(shè)置scope='package',autouse=True。
使用命令行方式運行test_scope這個測試包中的所有模塊。查看運行結(jié)果,在test_scope中第一個測試函數(shù)調(diào)用login()的時候,login創(chuàng)建,并按照功能打印”登陸操作“。在test_scope中最后一個測試函數(shù)運行結(jié)束后,按照login()功能打印出”注銷操作“,并銷毀login()。
5.session:
使用package的演示代碼,需改conftest.py中login()的scope='session',然后我們直接在終端中使用命令行運行多個文件。
運行以下命令:

運行結(jié)果:

說明:
使用命令行方式運行test_scope這個測試包中的test_class.py和test_function.py。查看運行結(jié)果,在test_class.py中第一個測試函數(shù)調(diào)用login()的時候,login創(chuàng)建,并按照功能打印”登陸操作“。在test_function.py中最后一個測試函數(shù)運行結(jié)束后,按照login()功能打印出”注銷操作“,并銷毀login()
1.2 動態(tài)作用域(Dynamic scope)
在某些情況下,我希望在不更改代碼的情況下更改feature的作用域。pytest的從5.2版本開始,提供了動態(tài)作用域的方法,來解決這種情況。
通過官方文檔的說明,我們了解到,可以將一個可調(diào)用對象傳遞給scope來實現(xiàn)動態(tài)作用域。這個可調(diào)用對象必須滿足以下三個條件:
1.這個可調(diào)用對象返回類型需是string,且必須是有效的scope級別,即只能返回"function"、"class"、"module"、"package"、"session"中的一個。
2.在fixture定義期間,這個可調(diào)用對象只能被執(zhí)行一次,不能被多次調(diào)用。
3.這個可調(diào)用對象定義的時候,必須使用fixture_name和config作為參數(shù)。
下面通過示例給大家演示以下用法。
示例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的作用域,這個函數(shù)按照pytest官方文檔的要求,帶有fixture_name 和config兩個關(guān)鍵字作為參數(shù),并且實現(xiàn)下述功能:
1.當用命令行運行測試時,運行命令中帶有‘-k'參數(shù),則被調(diào)的fixture函數(shù)的作用域為"function"
2.其余情況下,被調(diào)的fixture的函數(shù)為"class"。
login:一個使用動態(tài)作用域方法的fixture,其scope=dynamic_fixture_scope。為了方便演示,autouse也被設(shè)置為True。
下面我們分兩種情況來運行看看:
1.帶參數(shù)-k運行
運行命令:我們只運行測試名稱中包含test_A的測試函數(shù)。
pytest -vs -k 'test_A' test_dynamicscope.py
運行結(jié)果:

通過運行結(jié)果可以看到,當命令行中出現(xiàn)-k參數(shù)的時候,login的scope確實是function級別的。兩個被選中的測試函數(shù)分別在不同的測試類中,但是都調(diào)用了login。
2.無參數(shù)-k運行
運行命令:我們不帶參數(shù)-k運行。
pytest -vs test_dynamicscope.py
運行結(jié)果:

通過運行結(jié)果可以看到,無-k參數(shù)的時候,login的scope確實是class級別的。
2. fixture的實例化順序
根據(jù)官方文檔,我們了解到影響fixture實例化順序的三個因素是:
1. 作用域(scope)
2. 依賴項
3. 自動調(diào)用(autouse)
而fixture名稱、測試函數(shù)名稱以及定義它們的位置一般都不會影響執(zhí)行順序。
下面介紹一下這個三個因素是如何影響實例化順序的。
2.1 作用域級別高的fixture先執(zhí)行
我們直接使用官方文檔提供的示例來說明。
示例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"]
運行結(jié)果:

說明:
演示代碼中我們定義了:
六個fixture函數(shù):
order:scope為session級別,返回一個空list。func: 調(diào)用了order,scope為默認的function級別,并實現(xiàn)向order返回的列表中插入”function“的操作。cls:調(diào)用了order,scope為class級別,并實現(xiàn)向order返回的列表中插入”class“的操作。mod:調(diào)用了order,scope為module級別,并實現(xiàn)向order返回的列表中插入”module“的操作。pack:調(diào)用了order,scope為package級別,并實現(xiàn)向order返回的列表中插入”package“的操作。sess:調(diào)用了order,scope為session級別,并實現(xiàn)向order返回的列表中插入”session“的操作。
一個測試函數(shù):
test_order:主要目的是通過list中元素的順序來判斷是否按照預(yù)想的順序執(zhí)行fixture。
根據(jù)運行結(jié)果顯示,測試斷言通過,也就是fixture的執(zhí)行順序是按照我們預(yù)期的scope的級別由高到低去執(zhí)行的。
test_order調(diào)用fixture的順序是func, cls, mod, pack, sess, order,而實際執(zhí)行的順序是order, sess, pack, mod, cls, func。由此可見,我們在調(diào)用時定義的順序不會影響到fixture的實際執(zhí)行順序的。
官網(wǎng)執(zhí)行順序圖:

其中sess與order的scope都是session級別的,但是因為order是sess的依賴項,所以會先調(diào)用order,這個一點正好我們下面要說明。
2.2 fixture函數(shù)的依賴項先執(zhí)行
前面文章中有說過,fixture函數(shù)是可以調(diào)用另外一個fixture函數(shù)的。在執(zhí)行的時候,也是先執(zhí)行調(diào)用的那個fixture函數(shù)。舉了例子,fixture a 調(diào)用了fixture b,當測試函數(shù)調(diào)用fixture a的時候,會先去執(zhí)行fixture b 然后再執(zhí)行fixture a,最后執(zhí)行測試函數(shù)。
這是因為fixture b 是fixture a 的依賴項,fixture a 可能會需要fixture b提供一些條件才能正常被執(zhí)行。
下面我們來看一下官方文檔提供的一個例子,
示例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"]
運行結(jié)果:

說明:
演示代碼中定義了八個fixture函數(shù)和一個測試函數(shù),其中:
fixture函數(shù)有:orde、a、b、c、d、e、f、g
測試函數(shù)有:test_order
a調(diào)用了order;
b調(diào)用了a, order;
c調(diào)用了a, b, order;
d調(diào)用了c, b, order;
f調(diào)用了e, order;
g調(diào)用了f, c, order;
test_order調(diào)用了g、order。
我們來整理以上所有函數(shù)的依賴關(guān)系,他們之間的依賴關(guān)系如圖所示:

之前文章也有說過,在同一次測試執(zhí)行過程中,fixture是可以被多次調(diào)用的,但是不會被多次執(zhí)行。執(zhí)行一次后,fixture的實例對象和返回值會存在緩存中,下次再被調(diào)用的時候是直接從緩存中獲取的。
所以上面的順序我們可以再簡化一下:

執(zhí)行完所有依賴的fixture函數(shù)后,我們得到的order的結(jié)果為:['a','b','c','d','e'],因此測試斷言通過。
2.3 自動使用的fixture在其作用域內(nèi)首先執(zhí)行
根據(jù)官方文檔的說明,我們可以得到一下兩點重要內(nèi)容:
1.當測試函數(shù)調(diào)用的fixture的作用域級別都一樣,那么會首先調(diào)用自動使用的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"]
運行結(jié)果:

說明:
我們把示例3的代碼拿過來修改一下,把所有fixture的scope都改為function級別,并且對cls試著autouse=True。
test_order請求fixture的順序是:func, cls, mod, pack, sess。
當scope級別一樣的時候,且無依賴關(guān)系的時候,fixture的執(zhí)行順序應(yīng)該與調(diào)用順序一致,也應(yīng)該是func, cls, mod, pack, sess。但是實際運行的結(jié)果卻是['class', 'function', 'module', 'package', 'session']。由此可以判斷出,在scope一致且無依賴的情況下,自動執(zhí)行的fixture是最先被執(zhí)行的。
2.對一個autouse的fixture A來說,若調(diào)用了非autouse的fixture B,那么對于調(diào)用了fixture A的測試函數(shù)來說,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函數(shù)和一個測試函數(shù),其中:
fixture函數(shù)有:orde、a、b、c、d、e、f、g
測試函數(shù)有:test_order
a調(diào)用了order;
b調(diào)用了a, order;
c調(diào)用了 b, order,且是自動使用fixture;
d調(diào)用了 b, order;
f調(diào)用了e, order;
g調(diào)用了f, c, order;
test_order調(diào)用了g、order。
若c是非自動使用的fixture,代碼中fixture的一個執(zhí)行順序是什么樣的呢?
下圖是運行結(jié)果:

根據(jù)上面的運行結(jié)果和下面的順序圖,我們可以看到,除了g調(diào)用了c之外,其他函數(shù)都沒有調(diào)用c,而g也調(diào)用了f,所以現(xiàn)在還不清楚c應(yīng)該在d、e、f之前還是之后執(zhí)行。對c來說他只需在b之后、g之前執(zhí)行即可。而實際運行結(jié)果,c是在d、e、f之后和g之前執(zhí)行的。
對于pytest來說,fixture的執(zhí)行順序就是不明確,這種情況很可能會影響測試函數(shù)的執(zhí)行行為,影響了測試結(jié)果。

所以,我們需要明確fixture的執(zhí)行順序。
當我們使c為自動使用fixture時,這個順序又會發(fā)生什么變化呢?
下圖為運行結(jié)果:

根據(jù)上圖的運行結(jié)果和下圖的順序圖,我們可以看出來,當自動使用c后,它的執(zhí)行優(yōu)先級就比d要高,所以可以保證c是在d之前執(zhí)行的。這樣使執(zhí)行順序明確了。

當c是自動使用的fixture后,根據(jù)fixture函數(shù)的依賴項先執(zhí)行這一規(guī)則,c所依賴的b、b所依賴的a和a所依賴的order都會被執(zhí)行。這樣對于調(diào)用了c的g來說,order、a、b也相當于是自動使用的fixture了,但是當有其他fixture調(diào)用order、a、b時,order、a、b仍然保持自己的特性。
本節(jié)總結(jié)
我們來總結(jié)一下fixture實例化順序的一個規(guī)則:
1.fixture的scope級別越高,那么它執(zhí)行的優(yōu)先級越高。優(yōu)先級為:session>package>module>class>function
2.fixture如果存在依賴項,那么它所依賴的fixture函數(shù)會先被執(zhí)行。
3.同scope級別fixture中,自動使用的fixture會最先執(zhí)行;若該fixture存在依賴項,則對于調(diào)用了fixture的測試函數(shù)來說,這些依賴項也可以看做是自動使用的。
4.我們在編碼的時候最好可以依照以上規(guī)則為pytest提供一個清晰的fixture執(zhí)行順序,以免影響測試函數(shù)的行為和測試結(jié)果。
3. fixture的可用性
對于測試函數(shù)來說,能夠調(diào)用那些fixture,是由fixture定義的位置決定。
1.當fixture定義在一個測試類中時,只有該類中的測試函數(shù)可調(diào)用此fixture,其他測試函數(shù)無法調(diào)用該fixture。當測試函數(shù)在模塊的全局范圍內(nèi)定,則模塊下的所有測試函數(shù)都可以調(diào)用它。該特點與全局變量和局部變量相似。
示例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")
運行結(jié)果:

說明:
演示代碼中定義了兩個測試類:TestClass1和TestClass2。
TestClass1中定義了一個fixture——login,和測試函數(shù)test_case1,test_case1有調(diào)用login。
TestClass1中定義了一個測試函數(shù)test_case2,test_case2有調(diào)用login。
通過運行結(jié)果我們可以看到,與login在同一測試類中的test_case1成功調(diào)用了login,但是TestClass2中test_case2調(diào)用login的時候報錯了,報錯原因:未發(fā)現(xiàn)fixture函數(shù)“l(fā)ogin”。
2.conftest.py中定義的fixture,可以被同目錄下的所有模塊中定義的測試函數(shù)調(diào)用。
示例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")
運行結(jié)果:

說明:
我們在test_availability目錄下創(chuàng)建了一個conftest.py,并且定義了一個fixture——login。
在test_availability.py中,我們定義了兩個測試函數(shù)test_case1和test_case1,兩個測試函數(shù)都調(diào)用了login。
查看運行結(jié)果,測試函數(shù)都成功調(diào)用了login。
3.如果安裝的第三方插件提供了fixture,任何測試函數(shù)都可以正常使用,無需考慮位置因素。
文末說明:
以上內(nèi)容是我在閱讀pytest官方文檔后,依照個人理解進行整理。內(nèi)容可能會有理解錯誤之處,歡迎大家留言指正。謝謝
更多關(guān)于fixture作用域?qū)嵗樞蚣翱捎眯缘馁Y料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
-
python教程十行代碼教你語音轉(zhuǎn)文字QQ微信聊天
QQ上面發(fā)的語音消息是可以直接文字識別的,但是微信為什么沒有呢?是因為技術(shù)太難實現(xiàn)嗎?這個很簡單??!今天給大家介紹一下語音轉(zhuǎn)文字的原理 2021-09-09
-
Python基礎(chǔ)教程之內(nèi)置函數(shù)locals()和globals()用法分析
這篇文章主要介紹了Python基礎(chǔ)教程之內(nèi)置函數(shù)locals()和globals()用法,結(jié)合實例形式分析了locals()和globals()函數(shù)的功能、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下 2018-03-03
-
利用python在大量數(shù)據(jù)文件下刪除某一行的例子
今天小編就為大家分享一篇利用python在大量數(shù)據(jù)文件下刪除某一行的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 2019-08-08
最新評論
1. fixture的作用域
1.1 scope
通過前面文章的介紹,我們知道@pytest.fixture()有一個scope參數(shù),并且有五個作用域級別,這五個作用域級別的說明總結(jié)如下:
注意:fixture是在測試首次請求時創(chuàng)建的,并基于它們的作用域被銷毀。
| scope | 說明 |
|---|---|
| function | 默認值,函數(shù)或方法級別被調(diào)用,當函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀 |
| class | 類級別調(diào)用一次,當類中的最后一個測試函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀 |
| module | 模塊級別(每一個.py文件)調(diào)用一次,當模塊中最后一個測試函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀 |
| package | 包(一個文件夾下的.py文件)級別調(diào)用一次,當包中最后一個測試函數(shù)執(zhí)行結(jié)束后,fixture則被銷毀 |
| session | 多個文件調(diào)用一次,當多文件中最后一個測試函數(shù)執(zhí)行結(jié)束后,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")
運行結(jié)果:

說明:
演示代碼中,我們定義了一個fixture函數(shù)——login 和兩個測試函數(shù) test_case1 和test_case2。
裝飾器@pytest.fixture()沒有指定scope參數(shù),因此使用默認值:function。
test_case1 和test_case2在TestClass類中,且test_case1調(diào)用了login,test_case2未調(diào)用。
通過運行結(jié)果,我們可以看出來,scope=function的fixture——login,只在調(diào)用了它的test_case1這個測試函數(shù)前后執(zhí)行。test_case2未執(zhí)行。
2.class:
演示代碼:
import pytest
@pytest.fixture(scope='class') #名為login的fixture,完成登陸和注冊操作
def login():
print('登陸操作')
yield
print('注銷操作')
class TestClass1:
def test_case1(self,login): #調(diào)用了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): #調(diào)用了login
print("TestClass2:test_case2")
@pytest.mark.usefixtures("login") #TestClass3這個類,使用usefixtures調(diào)用了login
class TestClass3:
def test_case1(self):
print("TestClass3:test_case1")
def test_case2(self):
print("TestClass3:test_case2")
運行結(jié)果:

說明:
演示代碼中我們定義了一個fixture——login;三個測試類——TestClass1、TestClass2和TestClass3,這個三個測試類中都定了兩個測試函數(shù)——test_case1和test_case2。
對fixture函數(shù)我們聲明了其作用域——scope='class'。
TestClass1中,只有test_case1調(diào)用了login;
TestClass2中,只有test_case1調(diào)用了login;
TestClass3,我們使用@pytest.mark.usefixtures("login"),讓整個類調(diào)用了login。
查看運行結(jié)果我們可以發(fā)現(xiàn):
TestClass1中,test_case1運行前有”登陸操作“打印,并在test_case2執(zhí)行后有”注銷操作“顯打印。
TestClass2中,只有test_case2運行前有”登陸操作“打印,并在test_case2執(zhí)行后有”注銷操作“打印。而test_case1執(zhí)行前后并無任何數(shù)據(jù)打印。
TestClass3則與Testclass1執(zhí)行后數(shù)據(jù)顯示一致。
TestClass1和TestClass2結(jié)果的不同是由什么原因造成的呢?
官方文檔在介紹fixture的scope的時候,有這樣一句話,上文中也有提到:fixture是在測試首次請求時創(chuàng)建的,并基于它們的作用域被銷毀。(Fixtures are created when first requested by a test, and are destroyed based on their scope。)
對于TestClass1,test_case1調(diào)用了login,因此會在test_case1執(zhí)行的時候,創(chuàng)建login,并在其執(zhí)行之前打印了”登陸操作“。又根據(jù)login的scope——class,在TestClass1的最后一個測試函數(shù)test_case2執(zhí)行后,根據(jù)login的功能打印”注銷操作“,并銷毀login。
對于TestClass2,login是由test_case2調(diào)用的,而test_case1比test_case2先執(zhí)行,所以test_case1執(zhí)行的時候login還未被調(diào)用,fixture未被創(chuàng)建。直到執(zhí)行test_case2時,login才被創(chuàng)建并進行了對應(yīng)的操作。
后面其他類型的作用域示例中將不會在演示此種情況。
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")
運行結(jié)果:

說明:
演示代碼中,我們定義了:
一個fixture——login(),并且聲明其scope=”module“;
兩個測試類——TestClass1和TestClass2;
兩個測試類中分別定義了兩個測試函數(shù)——test_case1和test_case2;
TestClass1使用@pytest.mark.usefixtures("login")的方法,調(diào)用了login()。
運行結(jié)果顯示:
執(zhí)行時,自第一個測試函數(shù)TestClass1中的test_case1調(diào)用login()時,該fixture被創(chuàng)建,并且按照login()實現(xiàn)的功能打印"登陸操作",并在當前模塊中(一個.py文件)最后一個測試函數(shù)TestClass2中的test_case2執(zhí)行結(jié)束后,按照login()實現(xiàn)的功能打印"注銷操作",然后被銷毀。
4.package:
演示代碼:
首先看一下測試包目錄,新增一個conftest.py。里面所有的模塊都是上面的演示代碼。修改這些模塊里代碼,注釋調(diào)feature函數(shù),刪掉調(diào)用即可。

conftest.py
import pytest
@pytest.fixture(scope='package',autouse=True) #名為login的fixture,完成登陸和注冊操作
def login():
print('登陸操作')
yield
print('注銷操作')
使用命令行運行:

運行結(jié)果:

說明:
conftest.py中聲明了一個fixture——login(),并且設(shè)置scope='package',autouse=True。
使用命令行方式運行test_scope這個測試包中的所有模塊。查看運行結(jié)果,在test_scope中第一個測試函數(shù)調(diào)用login()的時候,login創(chuàng)建,并按照功能打印”登陸操作“。在test_scope中最后一個測試函數(shù)運行結(jié)束后,按照login()功能打印出”注銷操作“,并銷毀login()。
5.session:
使用package的演示代碼,需改conftest.py中login()的scope='session',然后我們直接在終端中使用命令行運行多個文件。
運行以下命令:

運行結(jié)果:

說明:
使用命令行方式運行test_scope這個測試包中的test_class.py和test_function.py。查看運行結(jié)果,在test_class.py中第一個測試函數(shù)調(diào)用login()的時候,login創(chuàng)建,并按照功能打印”登陸操作“。在test_function.py中最后一個測試函數(shù)運行結(jié)束后,按照login()功能打印出”注銷操作“,并銷毀login()
1.2 動態(tài)作用域(Dynamic scope)
在某些情況下,我希望在不更改代碼的情況下更改feature的作用域。pytest的從5.2版本開始,提供了動態(tài)作用域的方法,來解決這種情況。
通過官方文檔的說明,我們了解到,可以將一個可調(diào)用對象傳遞給scope來實現(xiàn)動態(tài)作用域。這個可調(diào)用對象必須滿足以下三個條件:
1.這個可調(diào)用對象返回類型需是string,且必須是有效的scope級別,即只能返回"function"、"class"、"module"、"package"、"session"中的一個。
2.在fixture定義期間,這個可調(diào)用對象只能被執(zhí)行一次,不能被多次調(diào)用。
3.這個可調(diào)用對象定義的時候,必須使用fixture_name和config作為參數(shù)。
下面通過示例給大家演示以下用法。
示例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的作用域,這個函數(shù)按照pytest官方文檔的要求,帶有fixture_name 和config兩個關(guān)鍵字作為參數(shù),并且實現(xiàn)下述功能:
1.當用命令行運行測試時,運行命令中帶有‘-k'參數(shù),則被調(diào)的fixture函數(shù)的作用域為"function"
2.其余情況下,被調(diào)的fixture的函數(shù)為"class"。
login:一個使用動態(tài)作用域方法的fixture,其scope=dynamic_fixture_scope。為了方便演示,autouse也被設(shè)置為True。
下面我們分兩種情況來運行看看:
1.帶參數(shù)-k運行
運行命令:我們只運行測試名稱中包含test_A的測試函數(shù)。
pytest -vs -k 'test_A' test_dynamicscope.py
運行結(jié)果:

通過運行結(jié)果可以看到,當命令行中出現(xiàn)-k參數(shù)的時候,login的scope確實是function級別的。兩個被選中的測試函數(shù)分別在不同的測試類中,但是都調(diào)用了login。
2.無參數(shù)-k運行
運行命令:我們不帶參數(shù)-k運行。
pytest -vs test_dynamicscope.py
運行結(jié)果:

通過運行結(jié)果可以看到,無-k參數(shù)的時候,login的scope確實是class級別的。
2. fixture的實例化順序
根據(jù)官方文檔,我們了解到影響fixture實例化順序的三個因素是:
1. 作用域(scope)
2. 依賴項
3. 自動調(diào)用(autouse)
而fixture名稱、測試函數(shù)名稱以及定義它們的位置一般都不會影響執(zhí)行順序。
下面介紹一下這個三個因素是如何影響實例化順序的。
2.1 作用域級別高的fixture先執(zhí)行
我們直接使用官方文檔提供的示例來說明。
示例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"]
運行結(jié)果:

說明:
演示代碼中我們定義了:
六個fixture函數(shù):
order:scope為session級別,返回一個空list。func: 調(diào)用了order,scope為默認的function級別,并實現(xiàn)向order返回的列表中插入”function“的操作。cls:調(diào)用了order,scope為class級別,并實現(xiàn)向order返回的列表中插入”class“的操作。mod:調(diào)用了order,scope為module級別,并實現(xiàn)向order返回的列表中插入”module“的操作。pack:調(diào)用了order,scope為package級別,并實現(xiàn)向order返回的列表中插入”package“的操作。sess:調(diào)用了order,scope為session級別,并實現(xiàn)向order返回的列表中插入”session“的操作。
一個測試函數(shù):
test_order:主要目的是通過list中元素的順序來判斷是否按照預(yù)想的順序執(zhí)行fixture。
根據(jù)運行結(jié)果顯示,測試斷言通過,也就是fixture的執(zhí)行順序是按照我們預(yù)期的scope的級別由高到低去執(zhí)行的。
test_order調(diào)用fixture的順序是func, cls, mod, pack, sess, order,而實際執(zhí)行的順序是order, sess, pack, mod, cls, func。由此可見,我們在調(diào)用時定義的順序不會影響到fixture的實際執(zhí)行順序的。
官網(wǎng)執(zhí)行順序圖:

其中sess與order的scope都是session級別的,但是因為order是sess的依賴項,所以會先調(diào)用order,這個一點正好我們下面要說明。
2.2 fixture函數(shù)的依賴項先執(zhí)行
前面文章中有說過,fixture函數(shù)是可以調(diào)用另外一個fixture函數(shù)的。在執(zhí)行的時候,也是先執(zhí)行調(diào)用的那個fixture函數(shù)。舉了例子,fixture a 調(diào)用了fixture b,當測試函數(shù)調(diào)用fixture a的時候,會先去執(zhí)行fixture b 然后再執(zhí)行fixture a,最后執(zhí)行測試函數(shù)。
這是因為fixture b 是fixture a 的依賴項,fixture a 可能會需要fixture b提供一些條件才能正常被執(zhí)行。
下面我們來看一下官方文檔提供的一個例子,
示例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"]
運行結(jié)果:

說明:
演示代碼中定義了八個fixture函數(shù)和一個測試函數(shù),其中:
fixture函數(shù)有:orde、a、b、c、d、e、f、g
測試函數(shù)有:test_order
a調(diào)用了order;
b調(diào)用了a, order;
c調(diào)用了a, b, order;
d調(diào)用了c, b, order;
f調(diào)用了e, order;
g調(diào)用了f, c, order;
test_order調(diào)用了g、order。
我們來整理以上所有函數(shù)的依賴關(guān)系,他們之間的依賴關(guān)系如圖所示:

之前文章也有說過,在同一次測試執(zhí)行過程中,fixture是可以被多次調(diào)用的,但是不會被多次執(zhí)行。執(zhí)行一次后,fixture的實例對象和返回值會存在緩存中,下次再被調(diào)用的時候是直接從緩存中獲取的。
所以上面的順序我們可以再簡化一下:

執(zhí)行完所有依賴的fixture函數(shù)后,我們得到的order的結(jié)果為:['a','b','c','d','e'],因此測試斷言通過。
2.3 自動使用的fixture在其作用域內(nèi)首先執(zhí)行
根據(jù)官方文檔的說明,我們可以得到一下兩點重要內(nèi)容:
1.當測試函數(shù)調(diào)用的fixture的作用域級別都一樣,那么會首先調(diào)用自動使用的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"]
運行結(jié)果:

說明:
我們把示例3的代碼拿過來修改一下,把所有fixture的scope都改為function級別,并且對cls試著autouse=True。
test_order請求fixture的順序是:func, cls, mod, pack, sess。
當scope級別一樣的時候,且無依賴關(guān)系的時候,fixture的執(zhí)行順序應(yīng)該與調(diào)用順序一致,也應(yīng)該是func, cls, mod, pack, sess。但是實際運行的結(jié)果卻是['class', 'function', 'module', 'package', 'session']。由此可以判斷出,在scope一致且無依賴的情況下,自動執(zhí)行的fixture是最先被執(zhí)行的。
2.對一個autouse的fixture A來說,若調(diào)用了非autouse的fixture B,那么對于調(diào)用了fixture A的測試函數(shù)來說,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函數(shù)和一個測試函數(shù),其中:
fixture函數(shù)有:orde、a、b、c、d、e、f、g
測試函數(shù)有:test_order
a調(diào)用了order;
b調(diào)用了a, order;
c調(diào)用了 b, order,且是自動使用fixture;
d調(diào)用了 b, order;
f調(diào)用了e, order;
g調(diào)用了f, c, order;
test_order調(diào)用了g、order。
若c是非自動使用的fixture,代碼中fixture的一個執(zhí)行順序是什么樣的呢?
下圖是運行結(jié)果:

根據(jù)上面的運行結(jié)果和下面的順序圖,我們可以看到,除了g調(diào)用了c之外,其他函數(shù)都沒有調(diào)用c,而g也調(diào)用了f,所以現(xiàn)在還不清楚c應(yīng)該在d、e、f之前還是之后執(zhí)行。對c來說他只需在b之后、g之前執(zhí)行即可。而實際運行結(jié)果,c是在d、e、f之后和g之前執(zhí)行的。
對于pytest來說,fixture的執(zhí)行順序就是不明確,這種情況很可能會影響測試函數(shù)的執(zhí)行行為,影響了測試結(jié)果。

所以,我們需要明確fixture的執(zhí)行順序。
當我們使c為自動使用fixture時,這個順序又會發(fā)生什么變化呢?
下圖為運行結(jié)果:

根據(jù)上圖的運行結(jié)果和下圖的順序圖,我們可以看出來,當自動使用c后,它的執(zhí)行優(yōu)先級就比d要高,所以可以保證c是在d之前執(zhí)行的。這樣使執(zhí)行順序明確了。

當c是自動使用的fixture后,根據(jù)fixture函數(shù)的依賴項先執(zhí)行這一規(guī)則,c所依賴的b、b所依賴的a和a所依賴的order都會被執(zhí)行。這樣對于調(diào)用了c的g來說,order、a、b也相當于是自動使用的fixture了,但是當有其他fixture調(diào)用order、a、b時,order、a、b仍然保持自己的特性。
本節(jié)總結(jié)
我們來總結(jié)一下fixture實例化順序的一個規(guī)則:
1.fixture的scope級別越高,那么它執(zhí)行的優(yōu)先級越高。優(yōu)先級為:session>package>module>class>function
2.fixture如果存在依賴項,那么它所依賴的fixture函數(shù)會先被執(zhí)行。
3.同scope級別fixture中,自動使用的fixture會最先執(zhí)行;若該fixture存在依賴項,則對于調(diào)用了fixture的測試函數(shù)來說,這些依賴項也可以看做是自動使用的。
4.我們在編碼的時候最好可以依照以上規(guī)則為pytest提供一個清晰的fixture執(zhí)行順序,以免影響測試函數(shù)的行為和測試結(jié)果。
3. fixture的可用性
對于測試函數(shù)來說,能夠調(diào)用那些fixture,是由fixture定義的位置決定。
1.當fixture定義在一個測試類中時,只有該類中的測試函數(shù)可調(diào)用此fixture,其他測試函數(shù)無法調(diào)用該fixture。當測試函數(shù)在模塊的全局范圍內(nèi)定,則模塊下的所有測試函數(shù)都可以調(diào)用它。該特點與全局變量和局部變量相似。
示例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")
運行結(jié)果:

說明:
演示代碼中定義了兩個測試類:TestClass1和TestClass2。
TestClass1中定義了一個fixture——login,和測試函數(shù)test_case1,test_case1有調(diào)用login。
TestClass1中定義了一個測試函數(shù)test_case2,test_case2有調(diào)用login。
通過運行結(jié)果我們可以看到,與login在同一測試類中的test_case1成功調(diào)用了login,但是TestClass2中test_case2調(diào)用login的時候報錯了,報錯原因:未發(fā)現(xiàn)fixture函數(shù)“l(fā)ogin”。
2.conftest.py中定義的fixture,可以被同目錄下的所有模塊中定義的測試函數(shù)調(diào)用。
示例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")
運行結(jié)果:

說明:
我們在test_availability目錄下創(chuàng)建了一個conftest.py,并且定義了一個fixture——login。
在test_availability.py中,我們定義了兩個測試函數(shù)test_case1和test_case1,兩個測試函數(shù)都調(diào)用了login。
查看運行結(jié)果,測試函數(shù)都成功調(diào)用了login。
3.如果安裝的第三方插件提供了fixture,任何測試函數(shù)都可以正常使用,無需考慮位置因素。
文末說明:
以上內(nèi)容是我在閱讀pytest官方文檔后,依照個人理解進行整理。內(nèi)容可能會有理解錯誤之處,歡迎大家留言指正。謝謝
更多關(guān)于fixture作用域?qū)嵗樞蚣翱捎眯缘馁Y料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python教程十行代碼教你語音轉(zhuǎn)文字QQ微信聊天
QQ上面發(fā)的語音消息是可以直接文字識別的,但是微信為什么沒有呢?是因為技術(shù)太難實現(xiàn)嗎?這個很簡單??!今天給大家介紹一下語音轉(zhuǎn)文字的原理2021-09-09
Python基礎(chǔ)教程之內(nèi)置函數(shù)locals()和globals()用法分析
這篇文章主要介紹了Python基礎(chǔ)教程之內(nèi)置函數(shù)locals()和globals()用法,結(jié)合實例形式分析了locals()和globals()函數(shù)的功能、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下2018-03-03
利用python在大量數(shù)據(jù)文件下刪除某一行的例子
今天小編就為大家分享一篇利用python在大量數(shù)據(jù)文件下刪除某一行的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08

