前言

個人認為,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')

執行結果如下: