天天看點

Pytest學習筆記2——前後置處理進階函數Fixture(完整篇)

目錄

  引言

  Pytest簡介

  Fixture函數定義

  Scope參數介紹與使用

  Autouse參數介紹與使用

  Params參數介紹與使用

  Ids參數介紹與使用

  Name參數介紹與使用

  總結

  引言

  前面介紹了pytest傳統的前後置處理方法,通過一些執行個體,知道了它對處理前後置的場景是有一定的局限性。是以才引入fixture裝飾器函數,fixture是pytest的核心功能,也是亮點功能,它可以靈活的處理很多特殊的場景,利用pytest做接口測試,熟練掌握fixture的使用方法,pytest用起來才會得心應手!

  Pytest簡介

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

  1.有獨立的命名,并通過聲明它們從測試函數、子產品、類或整個項目中的使用來激活。

  2.按子產品化的方式實作,每個fixture都可以互相調用。

  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
):
    """
	1、fixture不管有沒有參數,都可以用來标記夾具功能;
	2、test子產品或類都可以使用'pytest.mark.usefixture(fixturename)'裝飾器來标記,标記之後就每個測試用例運作之前會調用fixturename;
	3、測試函數可以直接使用fixture名稱作為輸入參數,在這種情況下,fixture執行個體從fixture傳回函數将被注入。
	4、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",它的作用範圍是每個測試用例來之前運作一次,銷毀代碼在測試用例運作之後運作。

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

  (單個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測試用例開始前執行一次,我去掉之後,再運作結果如下:

Pytest學習筆記2——前後置處理進階函數Fixture(完整篇)

  作為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學習筆記2——前後置處理進階函數Fixture(完整篇)

  原理:

  在 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()
           

  

  運作結果:

Pytest學習筆記2——前後置處理進階函數Fixture(完整篇)

  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交流群學習與探索,更多幹貨與你分

享。