Pytest學習筆記3-fixture
前言
個人認為,fixture是pytest最精髓的地方,也是學習pytest必會的知識點。
fixture用途
- 用於執行測試前後的初始化操作,比如打開瀏覽器、準備測試數據、清除之前的測試數據等等
- 用於測試用例的前置條件,比如UI自動化的登錄操作,讀取config參數等
- 用於測試用例之間的參數和數據傳遞
fixture優勢
firture相對於unittest
中的setup和teardown來說應該有以下幾點優勢
- 命名方式更加的靈活,不局限於setup和teardown
- conftest.py 配置里可以實現數據共享,不需要import就能自動找到一些配置
- scope=”module” 可以實現多個.py跨文件共享前置, 每一個.py文件調用一次
- scope=”session” 以實現多個.py跨文件使用一個session來完成多個用例
fixture語法
fixture(callable_or_scope=None, *args, scope="function", params=None, autouse=False, ids=None, name=None)
- scope:fixture的作用域,默認為function
- autouse:默認為False,表示需要用例手動調用該fixture;當為True時,表示所有作用域內的測試用例都會自動調用該fixture
- name:裝飾器的名稱,同一模組的fixture相互調用建議使用不同的name
fixture定義
fixture通過@pytest.fixture()
裝飾器裝飾一個函數,這個函數就是一個fixture,實例如下:
# test_fixture.py
import pytest
@pytest.fixture()
def fixtureDemo():
return "一個fixture"
def test_fixture(fixtureDemo):
print("測試用例執行時調用了{}".format(fixtureDemo))
if __name__ == "__main__":
pytest.main(['-v', 'test_fixture.py'])
執行結果如下:
fixture調用
調用fixture有三種方式
- fixture名字作為用例函數的參數
- 使用@pytest.mark.usefixtures(‘fixture名稱’)裝飾器
- 使用autouse參數
fixture名字作為用例函數的參數
將fixture名稱作為參數傳入測試用例,如果fixture有返回值,那麼測試用例將會接收返回值
舉個🌰:
import pytest
@pytest.fixture()
def fixtureDemo():
return '打開瀏覽器'
def test_fixture(fixtureDemo):
print('執行測試用例的時候,先{}'.format(fixtureDemo))
class TestFixture(object):
def test_fixture_class(self, fixtureDemo):
print('在類中執行測試用例的時候,先 "{}"'.format(fixtureDemo))
執行結果如下:
使用@pytest.mark.usefixtures(‘fixture名稱’)裝飾器
每個函數或者類前使用@pytest.mark.usefixtures(‘fixture名稱’)裝飾器裝飾
舉個🌰:
import pytest
@pytest.fixture()
def fixtureDemo():
print('執行fixture')
@pytest.mark.usefixtures('fixtureDemo')
def test_fixture():
print('執行測試用例')
@pytest.mark.usefixtures('fixtureDemo')
class TestFixture(object):
def test_fixture_class(self):
print('在類中執行測試用例')
執行結果如下:
使用autouse參數
指定fixture的參數autouse=True,這樣同一作用域的每個測試用例會自動調用fixture,autouse為False時,需要手動調用fixture
舉個🌰:
import pytest
@pytest.fixture(autouse=True)
def fixtureDemo():
print('執行fixture')
def test_fixture():
print('執行測試用例')
class TestFixture(object):
def test_fixture_class(self):
print('在類中執行測試用例')
執行結果如下:
三種方式的區別
以上便是fixture的三種調用方式,那麼這三種方式有何不同呢,觀察🌰,可以看出:
如果在測試用例中需要使用到fixture中返回的參數,就只能使用第一種調用方式了,因為fixture中返回的數據是默認在fixture名字中存儲的。
fixture作用範圍
fixture參數中的scope
參數可以控制fixture的作用範圍,scope參數可以是session, module,class,function, 默認為function
- session 會話級別:是多個文件調用一次,可以跨.py文件調用,每個.py文件就是module(通常配合conftest.py文件使用);
- module 模組級別:模組里所有的用例執行前執行一次module級別的fixture;
- class 類級別 :每個類執行前都會執行一次class級別的fixture;
- function 函數級別:每個測試用例執行前都會執行一次function級別的fixture(前面的🌰)
說明:當 fixture 有返回值時,pytest 會把返回值快取起來,如果 fixture 在指定的作用域內被多次調用,只有第一次調用會真正的被執行,後續調用會使用被快取起來的返回值,而不是再執行一遍;
下面我們通過實例來對fixture的作用範圍進行了解
function級別
每個測試用例之前運行一次
舉個🌰:
import pytest
@pytest.fixture()
def fixtureDemo():
return "fixture"
def test_01(fixtureDemo):
print("測試用例1執行時調用了{}".format(fixtureDemo))
def test_02(fixtureDemo):
print("測試用例2執行時調用了{}".format(fixtureDemo))
運行結果如下:
class級別
如果一個class裡面有多個用例,都調用了此fixture,那麼fixture只在此class里所有用例開始前執行一次
舉個🌰:
import pytest
@pytest.fixture(scope="class")
def fixtureDemo():
print('執行fixture')
@pytest.mark.usefixtures('fixtureDemo')
class TestFixture(object):
def test_01(self):
print('在類中執行測試用例1')
def test_02(self):
print('在類中執行測試用例2')
運行結果如下:
module級別
在當前.py腳本裡面所有用例開始前只執行一次
舉個🌰:
import pytest
@pytest.fixture(scope="module")
def fixtureDemo():
print('執行fixture')
@pytest.mark.usefixtures('fixtureDemo')
def test_01():
print("執行測試用例1")
@pytest.mark.usefixtures('fixtureDemo')
class TestFixture(object):
def test_02(self):
print('在類中執行測試用例2')
def test_03(self):
print('在類中執行測試用例3')
運行結果如下:
session級別
session級別是可以跨模組調用的,如果多個模組下的用例只需調用一次fixture,可以設置scope=”session”,並且寫到conftest.py文件里。
conftest.py作用域:放到項目的根目錄下就可以全局調用了,如果放到某個package下,那就在該package內有效。
conftest.py的fixture調用方式,無需導入,直接使用
舉個🌰:
先新建一個conftest.py文件
import pytest
@pytest.fixture()
def fixturedemo():
print("執行fixture")
test_01.py
def test_01(fixturedemo):
print('執行測試用例1')
test_02.py
def test_02(fixturedemo):
print('執行測試用例2')
在當前目錄中使用命令 pytest -v -s
執行,結果如下:
可以看到,在執行不同模組中的測試用例前,都調用了fixture
案例說明
看完有點懵?我們再用一個🌰來看看
在3個測試方法中同時調用4個級別的fixture來看看效果
先新建一個conftest.py文件
import pytest
# 作用域 function
@pytest.fixture(scope='function')
def fix_func():
print('方法級:fix_func')
# 作用域 class
@pytest.fixture(scope='class')
def fix_class():
print('類級:fix_class')
# 作用域 module
@pytest.fixture(scope='module')
def fix_module():
print('模組級:fix_module')
# 作用域 session
@pytest.fixture(scope='session')
def fix_session():
print('會話級:fix_session')
test_demo.py
import pytest
class TestClass_1:
"""測試類1"""
@pytest.mark.usefixtures('fix_func')
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_module')
@pytest.mark.usefixtures('fix_session')
def test_func_1(self):
"""測試方法1"""
print("測試方法1")
@pytest.mark.usefixtures('fix_func')
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_module')
@pytest.mark.usefixtures('fix_session')
def test_func_2(self):
"""測試方法2"""
print("測試方法2")
class TestClass_2:
"""測試類2"""
@pytest.mark.usefixtures('fix_func')
@pytest.mark.usefixtures('fix_class')
@pytest.mark.usefixtures('fix_module')
@pytest.mark.usefixtures('fix_session')
def test_func_3(self):
"""測試方法3"""
print("測試方法3")
執行結果如下:
觀察運行結果,我們可以發現:
test_func_1 的調用全部被執行了;
test_func_2 的調用只有 function 級的被執行,因為 test_func_2 和 test_func_1 同屬於同一個 session、module、class,所以這三個都不執行,只有 function 執行;
test_func_3 的調用執行了類級和方法級,因為 test_func_3 屬於 另外一個類,所以 class 級會被再次調用
fixture關鍵字yield
前面講的是在用例前加前置條件,相當於setup,那麼類似teardown的功能如何使用fixture實現呢,我們可以使用yield關鍵字
yield關鍵字的作用其實和函數中的return關鍵字差不多,可以返回數據給調用者,唯一的不同是當函數執行遇到yield時,會停止執行,然後執行調用處的函數,調用的函數執行完後會繼續執行yield關鍵後面的程式碼
yield實現teardown
舉個🌰:
import pytest
@pytest.fixture(scope="module")
def fixtureDemo():
print('執行fixture')
yield
print('執行teardown操作')
def test_01(fixtureDemo):
print('執行測試用例1')
def test_02(fixtureDemo):
print('執行測試用例2')
運行結果如下:
yield遇到異常
如果多個用例運行時,有一個用例出現異常,不會影響yield後面的程式碼執行 , 運行結果互不影響,當全部用例執行完之後,yield後面的程式碼會正常執行
舉個🌰:
import pytest
@pytest.fixture(scope="module")
def fixtureDemo():
print('執行fixture')
yield
print('執行teardown操作')
def test_01(fixtureDemo):
print('執行測試用例1')
raise NameError # 模擬異常
def test_02(fixtureDemo):
print('執行測試用例2')
運行結果如下: