Pytest學習筆記2——先後置處理高級函數Fixture(完整篇)

  引言

  前面介紹了pytest傳統的先後置處理方法,經過一些實例,知道了它對處理先後置的場景是有必定的侷限性。因此才引入fixture裝飾器函數,fixture是pytest的核心功能,也是亮點功能,它能夠靈活的處理不少特殊的場景,利用pytest作接口測試,熟練掌握fixture的使用方法,pytest用起來纔會駕輕就熟!session

  Pytest簡介

  fixture的目的是提供一個固定基線,在該基線上測試能夠可靠地和重複地執行。fixture提供了區別於傳統單元測試(setup/teardown)有顯著改進:框架

  1.有獨立的命名,並經過聲明它們從測試函數、模塊、類或整個項目中的使用來激活。less

  2.按模塊化的方式實現,每一個fixture均可以互相調用。ide

  3.fixture的範圍從簡單的單元擴展到複雜的功能測試,容許根據配置和組件選項對fixture和測試用例進行參數化,或者跨函數function、類class、模塊module或整個測試會話sessio範圍。模塊化

  Fixture函數定義

  先看一下fixture的函數定義:函數

def fixture(
    callable_or_scope=None,
    *args,
    scope="function",
    params=None,
    autouse=False,
    ids=None,
    name=None
):
    """Decorator to mark a fixture factory function.

    This decorator can be used, with or without parameters, to define a
    fixture function.

    The name of the fixture function can later be referenced to cause its
    invocation ahead of running tests: test
    modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
    marker.

    Test functions can directly use fixture names as input
    arguments in which case the fixture instance returned from the fixture
    function will be injected.

    Fixtures can provide their values to test functions using ``return`` or ``yield``
    statements. When using ``yield`` the code block after the ``yield`` statement is executed
    as teardown code regardless of the test outcome, and must yield exactly once.

    :arg scope: the scope for which this fixture is shared, one of
                ``"function"`` (default), ``"class"``, ``"module"``,
                ``"package"`` or ``"session"`` (``"package"`` is considered **experimental**
                at this time).

                This parameter may also be a callable which receives ``(fixture_name, config)``
                as parameters, and must return a ``str`` with one of the values mentioned above.

                See :ref:`dynamic scope` in the docs for more information.

    :arg params: an optional list of parameters which will cause multiple
                invocations of the fixture function and all of the tests
                using it.
                The current parameter is available in ``request.param``.

    :arg autouse: if True, the fixture func is activated for all tests that
                can see it.  If False (the default) then an explicit
                reference is needed to activate the fixture.

    :arg ids: list of string ids each corresponding to the params
                so that they are part of the test id. If no ids are provided
                they will be generated automatically from the params.

    :arg name: the name of the fixture. This defaults to the name of the
                decorated function. If a fixture is used in the same module in
                which it is defined, the function name of the fixture will be
                shadowed by the function arg that requests the fixture; one way
                to resolve this is to name the decorated function
                ``fixture_<fixturename>`` and then use
                ``@pytest.fixture(name='<fixturename>')``.
    """

  

  大體翻譯了一下:單元測試

def fixture(
    callable_or_scope=None,
    *args,
    scope="function",
    params=None,
    autouse=False,
    ids=None,
    name=None
):
    """
	一、fixture無論有沒有參數,均可以用來標記夾具功能;
	二、test模塊或類均可以使用'pytest.mark.usefixture(fixturename)'裝飾器來標記,標記以後就每一個測試用例運行以前會調用fixturename;
	三、測試函數能夠直接使用fixture名稱做爲輸入參數,在這種狀況下,fixture實例從fixture返回函數將被注入。
	四、fixture可使用' return '或' yield '來提供它們的值來測試函數語句。當使用'yield'語句後的代碼塊被執行不管測試結果如何,都必須精確地產生一次。

    :arg scope: scope做用域有4個級別,默認是function,其次class,而後是module和session.

    :arg params: 一個可選的形參列表,它將致使多個參數對夾具功能和全部測試的調用使用它。

    :arg autouse:若是爲真,則對全部測試激活fixture func能夠看到它。若是爲False(默認值),則顯式須要引用來激活夾具。 

    :arg ids: 每一個參數對應的字符串id列表所以它們是測試id的一部分。若是沒有提供id它們將由參數自動生成。

    :arg name:設備的名稱。方法的默認名稱裝飾功能。若是在同一模塊中使用了一個fixture哪一個定義了,夾具的函數名會是被要求夾具的功能參數所遮蔽;的一種方法要解決這個問題,能夠命名修飾後的函數'fixture_<fixturename>'而後使用
@pytest.fixture (name = ' < fixturename > ')。
    """
	
	

  

  Scope參數介紹與使用

  scope參數主要控制fixture做用範圍,邏輯優先級是:session > module > class > function.
  scope參數有四種選擇:function(測試函數級別),class(測試類級別),module(測試模塊「.py」級別),session(多個文件級別)。默認是function級別。
  這裏須要注意的pytest文檔中講的模塊是針對".py"文件的叫法。也就是模塊就是py文件的意思。
  學習

  級別介紹:
  function級別(針對函數):每一個測試用例運行以前運行
  class級別(針對測試類):每一個類執行一次(全部測試用例運行以前運行,這個節點從引入fixture的測試用例開始算),一個類能夠有多個測試方法(用例)。
  module級別(針對單模塊):每一個模塊(.py)執行一次,無論類中測試方法仍是類外的測試方法。
  session級別(針對多模塊):是多個文件調用一次,能夠跨.py文件調用,每一個.py文件就是module。測試

  Fixture做用範圍:scope = 'function'

  @pytest.fixture()函數使用方式:做爲參數傳入(單個)

  裝飾器@pytest.fixture()若是不寫參數,默認就是scope="function",它的做用範圍是每一個測試用例來以前運行一次,銷燬代碼在測試用例運行以後運行。this

  以前的文章已經介紹過了,這裏再貼一下代碼:

  (單個fixture函數,沒有類)

# 建立fixture函數(無類)——法1,做爲參數傳入,做爲範圍:functions
@pytest.fixture()
def login():
	print("輸入帳號")
	a = "account"
	return a


def test_001(login):
	print("帳號是: %s"%login)
	assert login == "account"

def test_002():
	print("單擊登錄")

if __name__ == '__main__':
    pytest.main()

  運行結果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 2 items                                                                                                                                                                       

fixtrue_001.py 輸入帳號
帳號是: account
.單擊登錄
.

================================================================================== 2 passed in 0.02s ===================================================================================

  (單個fixture函數,有類)

# 建立fixture函數(類中)——法2,做爲參數傳入,做爲範圍:functions

@pytest.fixture(scope="function")
def login():
	print("輸入帳號")
	a = "account"
	return a


class TestLogin:
	def test_001(self,login):
		print("輸入的帳號: %s"%login)
		assert login == "account"
	def test_002(self):
		print("")


if __name__ == '__main__':
	pytest.main(["-s","fixtrue_001.py"])

  運行結果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 2 items                                                                                                                                                                       

fixtrue_001.py 輸入帳號
輸入的帳號: account
.用例2
.

================================================================================== 2 passed in 0.02s ===================================================================================

  

  @pytest.fixture()函數使用方式:做爲參數傳入(多個fixture使用)

   一些場景,好比登錄以後有退出,這樣的話須要兩個fixture函數處理,示例以下:

# fixture函數(類中) 做爲多個參數傳入
@pytest.fixture()
def login():
	print("輸入帳號")
	a = "account"
	return a

@pytest.fixture()
def logout():
	print("退出")

class TestLogin:
	def test_001(self,login):
		print("輸入的帳號: %s"%login)
		assert login == "account"
	def test_002(self,logout):
		print("退出")
	def test_003(self,login,logout):
		print("步驟1:%s"%login)
		print("步驟2:%s"%logout)


if __name__ == '__main__':
    pytest.main()

  

  運行結果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 3 items                                                                                                                                                                       

fixtrue_001.py 輸入帳號
輸入的帳號: account
.退出
退出
.輸入帳號
退出
步驟1:account
步驟2:None
.

================================================================================== 3 passed in 0.03s ===================================================================================

  

  @pytest.fixture()函數使用方式:做爲參數傳入(互相調用)

  fixture固件能夠被測試方法調用,也能夠被固件本身調用。

  舉個例子:

# fixtrue做爲參數,互相調用傳入
@pytest.fixture()
def account():
	a = "account"
	print("輸入帳號:%s"%a)

@pytest.fixture()
def login(account):
	print("單擊登錄")

class TestLogin:
	def test_1(self,login):
		print("操做結果:%s"%login)
	def test_2(self,account):
		print("帳號: %s"%account)
	def test_3(self):
		print("測試用例3")

if __name__ == '__main__':
    pytest.main()

  

  運行結果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 3 items                                                                                                                                                                       

fixtrue_001.py 輸入帳號:account
單擊登錄
操做結果:None
.輸入帳號:account
帳號: None
.測試用例3
.

================================================================================== 3 passed in 0.02s ===================================================================================

  Fixture做用範圍:scope = 'class'

  fixture是class級別的時候,分爲兩種狀況:

  第一種,測試類下面全部測試方法(用例),都使用了fixture函數名,這樣的話,fixture只在該class下全部測試用例執行前執行一次。

  示例演示:

# fixture做用域 scope = 'class'
@pytest.fixture(scope='class')
def login():
	a = '123'
	print("輸入帳號密碼登錄")



class TestLogin:
	def test_1(self,login):
		print("用例1")
	def test_2(self,login):
		print("用例2")
	def test_3(self,login):
		print("用例3")
if __name__ == '__main__':
    pytest.main()

  

  運行結果:

collected 3 items                                                                                                                                                                       

fixtrue_001.py 輸入帳號密碼登錄
用例1
.用例2
.用例3
.

================================================================================== 3 passed in 0.02s ===================================================================================

  

  第二種,測試類下面只有一些測試方法使用了fixture函數名,這樣的話,fixture只在該class下第一個使用fixture函數的測試用例位置開始算,後面全部的測試用例執行前只執行一次。而該位置以前的測試用例就無論。

# fixture做用域 scope = 'class'
@pytest.fixture(scope='class')
def login():
	a = '123'
	print("輸入帳號密碼登錄")



class TestLogin:
	def test_1(self):
		print("用例1")
	def test_2(self,login):
		print("用例2")
	def test_3(self,login):
		print("用例3")
	def test_4(self):
		print("用例4")
if __name__ == '__main__':
    pytest.main()

  

  運行結果:

collected 4 items                                                                                                                                                                       

fixtrue_001.py 用例1
.輸入帳號密碼登錄
用例2
.用例3
.用例4
.

================================================================================== 4 passed in 0.03s ===================================================================================

 

  Fixture做用範圍:scope = 'module'

  fixture爲module時,對當前模塊(.py)文件下全部測試用例開始前執行一次,示例以下:

# fixture scope = 'module'
@pytest.fixture(scope='module')
def login():
	print("登錄")

def test_01(login):
	print("用例01")
def test_02(login):
	print("用例02")

class TestLogin():
	def test_1(self,login):
		print("用例1")
	def test_2(self,login):
		print("用例2")
	def test_3(self,login):
		print("用例3")

if __name__ == '__main__':
    pytest.main()

  

  運行結果:

collected 5 items                                                                                                                                                                       

fixtrue_001.py 登錄
用例01
.用例02
.用例1
.用例2
.用例3
.

================================================================================== 5 passed in 0.03s ===================================================================================

  

  Fixture做用範圍:scope = 'session'

  設置方式和module級別的設置方式同樣,須要注意的是session級別通常都是多個.py文件共用,因此要前置函數的聲明通常在conftest.py中。

  其做用在多個測試模塊(.py文件)中只執行一次,而且是在傳入函數名的測試用例中的第一個執行的測試用例以前執行。

  若是在同一個模塊下(.py文件裏),session與module特性一致,示例以下:

import pytest
@pytest.fixture(scope="session")
def login():
    print("\n輸入用戶名密碼登錄! configtest")
    yield
    print("退出登錄")


def test_cart(login):
    print('用例1,登錄後執行添加購物車功能操做')

def test_search():
    print('用例2,不登錄查詢功能操做')

def test_pay(login):
    print('用例3,登錄後執行支付功能操做')

if __name__ == '__main__':
    pytest.main()

  

  運行結果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 3 items                                                                                                                                                                       

fixtrue_003.py
輸入用戶名密碼登錄! configtest
用例1,登錄後執行添加購物車功能操做
.用例2,不登錄查詢功能操做
.用例3,登錄後執行支付功能操做
.退出登錄


================================================================================== 3 passed in 0.02s ===================================================================================

  

  @pytest.fixture()函數使用方式:做爲conftest.py文件傳入

  若是在不一樣模塊下(.py文件裏),session是給多個.py文件使用,而且寫到conftest.py文件裏,conftest.py文件名稱是固定的,pytest會自動識別該文件。

  放到工程的根目錄下,就能夠全局調用了,若是放到某個package包下,那就只在該package內有效,示例以下:

  在文件夾fixture_exp下新建conftest.py文件:

# fixture 固定裝飾器,做用域:scope = 'session'
import pytest
@pytest.fixture(scope='session')
def login():
    print("輸入帳號密碼")
    yield
    print("清理數據完成")

  

  新建兩個測試文件:

# fixture scope = 'session',fixtrue_001.py
class TestLogin1():
	def test_1(self,login):
		print("用例1")
	def test_2(self):
		print("用例2")
	def test_3(self,login):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

# fixture scope = 'session',fixtrue_002.py
import pytest

class TestLogin2():
	def test_4(self):
		print("用例4")
	def test_5(self):
		print("用例5")
	def test_6(self):
		print("用例6")


if __name__ == '__main__':
    pytest.main()

  同時運行兩個測試文件,能夠在控制檯中輸入:

pytest -s fixtrue_001.py fixtrue_002.py

  

  運行結果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 6 items                                                                                                                                                                       

fixtrue_001.py 輸入帳號密碼
用例1
.用例2
.用例3
.
fixtrue_002.py 用例4
.用例5
.用例6
.清理數據完成


================================================================================== 6 passed in 0.04s ===================================================================================

  上面的例子,若是test_1測試用例沒有傳fixture函數名login的話,fixture裝置將在執行test_3測試用例開始前執行一次,我去掉以後,再運行結果以下:

  做爲conftest.py文件傳入(擴展)

  上面講的fixture做用域是session,通常結合conftest.py來使用,也就是做爲conftest.py文件傳入。

  使用背景:若是咱們有不少個前置函數,寫在各個py文件中是不很亂?再或者說,咱們不少個py文件想要使用同一個前置函數該怎麼辦?這也就是conftest.py的做用。

  使用conftest.py的規則:

  conftest.py這個文件名是固定的,不能夠更改。
  conftest.py與運行用例在同一個包下,而且該包中有__init__.py文件
  使用的時候不須要導入conftest.py,會自動尋找。
  來看個小栗子:咱們新建了一個conftest.py文件,將前置函數的聲明寫在裏面;在同一包下又新建了一個測試模塊,在測試方法中傳入了conftest.py中聲明的前置函數名。

# fixture 固定裝飾器,做用域:scope = 'session'
import pytest
@pytest.fixture()
def login():
    print("輸入帳號密碼")
    yield
    print("清理數據完成")

  

import pytest
# fixture scope = 'session',fixtrue_001.py
class TestLogin1():
	def test_1(self,login):
		print("用例1")
	def test_2(self):
		print("用例2")
	def test_3(self,login):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

  運行結果:

fixtrue_001.py 輸入帳號密碼
用例1
.清理數據完成
用例2
.輸入帳號密碼
用例3
.清理數據完成


================================================================================== 3 passed in 0.02s ===================================================================================

  

  上面的栗子能夠換一種寫法,但須要利用另一個裝飾器。

  咱們在conftest.py中聲明完前置函數後,在測試模塊中除了使用傳入函數名的方式,還可使用@pytest.mark.userfixtures()裝飾器。

  舉個小栗子:聲明前置函數的過程和上面同樣;咱們在每一個測試方法上都加了@pytest.mark.userfixtures()裝飾器,傳入了前置函數名做爲參數;運行結果和上圖同樣便再也不展現。

import pytest
# fixture scope = 'session',fixtrue_001.py
class TestLogin1():
	@pytest.mark.usefixtures('login')
	def test_1(self):
		print("用例1")
	@pytest.mark.usefixtures('login')
	def test_2(self):
		print("用例2")
	def test_3(self):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

fixtrue_001.py 輸入帳號密碼
用例1
.清理數據完成
輸入帳號密碼
用例2
.清理數據完成
用例3
.

================================================================================== 3 passed in 0.02s ===================================================================================

  

  若是有100個測試方法,這樣就要寫100個裝飾器,是否是不方便?

  這個時候若是你想要模塊中的每一個測試用例都調用該固件,你也可使用pytestmark標記:以下代碼(注意pytestmark變量名不可更改),示例以下:

import pytest
# fixture scope = 'session',fixtrue_001.py
pytestmark = pytest.mark.usefixtures('login')
class TestLogin1():
	def test_1(self):
		print("用例1")
	def test_2(self):
		print("用例2")
	def test_3(self):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

  運行結果:

fixtrue_001.py 輸入帳號密碼
用例1
.清理數據完成
輸入帳號密碼
用例2
.清理數據完成
輸入帳號密碼
用例3
.清理數據完成


================================================================================== 3 passed in 0.02s ===================================================================================

  

  注意:能夠在測試函數前使用 @pytest.mark.usefixtures("fixture1","fixture2")標記測試函數或者測試類。與在測試方法中添加 fixture 參數差很少,可是使用 usefixtures 不能使用 fixture 的返回值。

  補充說明一下conftest.py文件的做用域是當前包內(包括子包);若是函數調用固件優先從當前測試類中尋找,而後是模塊(.py文件)中,接着是當前包中尋找(conftest.py中),若是沒有再找父包直至根目錄;若是咱們要聲明全局的conftest.py文件,咱們能夠將其放在根目錄下。

  conftest.py做用範圍:測試類 > .py文件 > package

 

  Autouse參數介紹與使用

  調用fixture四種方法

  1.函數或類裏面方法直接傳fixture的函數參數名稱

  2.使用裝飾器@pytest.mark.usefixtures()修飾

  3.使用pytestmark = pytest.mark.usefixtures('login')

  4.autouse=True自動使用

  前面三種已經講過,如今就是講第四種。

  咱們在作自動化測試的時候,用例是很是多,若是每條用例都要去傳入前置函數名或裝飾器,很不方便。

  這時咱們可使用@pytest.fixture()中的參數autouse(自動使用),將其設爲true(默認爲false),這樣每一個測試函數都會自動調用該前置函數了。

  舉個小栗子:

import pytest

@pytest.fixture(autouse="true")
def login():
    print("輸入帳號密碼")


class TestLogin:
    def test_1(self):
        print("用例1")
    def test_2(self):
        print("用例2")

if __name__ == '__main__':
    pytest.main()

  

  運行結果:

============================== 2 passed in 0.05s ==============================

Process finished with exit code 0
輸入帳號密碼
PASSED                              [ 50%]用例1
輸入帳號密碼
PASSED                              [100%]用例2

  

  注意:

  對於那些不依賴於任何系統狀態或者外部數據,又須要屢次運行的代碼,能夠在 fixture 中添加 autouse=True選項,例如 @pytest.fixture(autouse=True, scope="session")。

  可是,若是能夠的話,儘可能應當選擇參數傳遞或者 usefixtures 的方法而不是 autouse。autouse 會讓測試函數邏輯看上去沒有那麼清晰,更像是一個特例。

 

  Params參數介紹與使用

  前面介紹Fixture定義的時候講了params,:arg params: 一個可選的形參列表,它將致使多個參數對夾具功能和全部測試的調用使用它。

  1.fixture能夠帶參數,params支持列表;

  2.默認是None;

  3.對於param裏面的每一個值,fixture都會去調用執行一次,就像執行for循環同樣把params裏的值遍歷一次。

  

  舉個例子:

import pytest
seq = [1,2,3]

@pytest.fixture(params=seq)
def test_data(request):
    print("參數")
    return request.param


class TestData:
    def test_1(self,test_data):
        print("用例",test_data)

if __name__ == '__main__':
    pytest.main()

  

運行結果:

 

  原理:

  在 pytest 中有一個內建的 fixture 叫作 request,表明 fixture 的調用狀態。request 有一個字段 param,可使用相似 @pytest.fixture(param=tasks_list)的方式,在 fixture 中使用 request.param的方式做爲返回值供測試函數調用。其中 tasks_list 包含多少元素,該 fixture 就會被調用幾回,分別做用在每一個用到的測試函數上。

  Ids參數介紹與使用

  ids一般能夠與params一塊兒使用,因爲沒有指定 id,因此在輸出時 pytest 會以 fixture 名加上數字做爲標識,fixture 也能夠指定 id,例如@pytest.fixture(param=tasks_list,ids=task_ids)  ids能夠是列表,也能夠是函數供 pytest 生成 task 標識。

  數字、字符串、布爾值和None將在測試ID中使用其一般的字符串表示形式,對於其餘對象,pytest會根據參數名稱建立一個字符串,能夠經過使用ids關鍵字參數來自定義用於測試ID的字符串。

 舉個例子:
import pytest
seq = [1,2,3]

@pytest.fixture(params=seq,ids=["a","b","c"])
def test_data(request):
    print("參數")
    # print(request)
    return request.param


class TestData:
    def test_1(self,test_data):
        print("用例",test_data)

if __name__ == '__main__':
    pytest.main()

  

  運行結果:

 

  Name參數介紹與使用

  一般來講使用 fixture 的測試函數會將 fixture 的函數名做爲參數傳遞,可是 pytest 也容許將 fixture 重命名。只須要使用 @pytest.fixture(name="new")便可,在測試函數中使用該 fixture 時只須要傳入 new 便可。
 
import pytest


@pytest.fixture(name="new_fixture")
def test_name():
    pass

def test_1(new_fixture):
    print("測試用例1")

  

  運行結果:

collecting ... collected 1 item

fixture_test03.py::test_1 PASSED                                         [100%]測試用例1


============================== 1 passed in 0.03s ==============================

  總結:默認使用fixture函數名稱做爲參數,也就是test_name做爲參數傳入,若是使用name,就須要將name的值做爲新的參數名稱傳遞給測試函數使用。

  總結

  以上就是pytest框架中fixture函數的介紹與使用,每種參數都介紹了一遍,原理和方法瞭解好,以便在實踐中駕輕就熟。若是對你有幫助或喜歡自動化測試開發的朋友,能夠加入右下方QQ交流羣學習與探索,更多幹貨與你分

享。

相關文章
相關標籤/搜索