前言
個人認為,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')
執行結果如下: