Pytest05-Fixture

5.Fixture

    在測試過程當中,fixture均可以派上用場。fixture是在測試函數運行先後,則pytest執行的外殼函數。fixture中的代碼能夠定製,知足多變的測試需求,包含定義傳入測試中的數據集、配置測試前系統的初始狀態、爲批量測試提供數據源等等。來看如下簡單示例,返回一個簡單的fixturehtml

import pytest

@pytest.fixture()
def getData():
    return 28

def test_getFixture(getData):
    assert getData==28
  • @pytest.fixture()裝飾器用於聲明函數是一個fixture。若是測試函數的參數列表中包含fixture名稱,則pytest會檢測到,並在測試函數運行以前執行該fixture。fixture能夠完成任務,也能夠返回數據給測試函數。python

  • test_getFixture()的參數列表中包含一個fixture,名爲getData,pytest會以該名稱搜索fixture。pytest會優先搜索該測試所在模塊,而後搜索conftest.pysql

後面所提到的fixture均是由@pytest.fixture()裝飾器定義的函數,fixture是pytest用於將測試先後進行預備、清理工做的代碼分離出核心邏輯的一種機制。數據庫

5.1 經過conftest.py共享fixture

    fixture的特色以下所示:微信

  • 1.fixture能夠放在單獨的測試文件中。若是但願多個測試文件共享fixture,能夠在某個公共目錄新建一個conftest.py文件,將fixture放在其中
  • 2.若是但願fixture的做用範圍僅限於某個測試文件,則能夠將fixture寫在該測試文件中
  • 3.儘管conftest.py是Python模塊,但卻不能被測試文件導入。所以是不容許出現import conftest的

5.2 使用fixture執行配置和銷燬邏輯

    在測試前準備和測試結束後清理環境,在數據庫中鏈接使用比較多。測試前須要鏈接數據庫,測試完成後,須要關閉數據庫等,這時就可使用fixture進行配置和清理環境了,以下所示:session

1.DBOperate.pyapp

import sqlite3
import os

class DBOperate:

    def __init__(self,dbPath=os.path.join(os.getcwd(),"db")):
        self.dbPath=dbPath
        self.connect=sqlite3.connect(self.dbPath)

    def Query(self,sql:str)->list:
        """傳統查詢語句"""
        queryResult = self.connect.cursor().execute(sql).fetchall()
        return queryResult if queryResult else []

    def QueryAsDict(self,sql:str)->dict:
        """調用該函數返回結果爲字典形式"""
        self.connect.row_factory=self.dictFactory
        cur=self.connect.cursor()
        queryResult=cur.execute(sql).fetchall()
        return queryResult if queryResult else {}

    def Insert(self,sql:str)->bool:
        insertRows=self.connect.cursor().execute(sql)
        self.connect.commit()
        return True if insertRows.rowcount else False

    def Update(self,sql:str)->bool:
        updateRows=self.connect.cursor().execute(sql)
        self.connect.commit()
        return  True if updateRows.rowcount else False


    def Delete(self,sql:str)->bool:
        delRows=self.connect.cursor().execute(sql)
        self.connect.commit()
        return True if delRows.rowcount else False

    def CloseDB(self):
        self.connect.cursor().close()
        self.connect.close()

    def dictFactory(self,cursor,row):
        """將sql查詢結果整理成字典形式"""
        d={}
        for index,col in enumerate(cursor.description):
            d[col[0]]=row[index]
        return d

2.conftest.py函數

import pytest
from DBOperate import DBOperate

@pytest.fixture()
def dbOperate():
    # setup:connect db
    db=DBOperate()
    # 數據庫操做
    sql="""SELECT * FROM user_info"""
    res=db.QueryAsDict(sql)
    # tearDown:close db
    db.CloseDB()
    return res

3.test_02.py測試

import pytest
from DBOperate import DBOperate

def test_dbOperate(dbOperate):
    db=DBOperate()
    sql = """SELECT * FROM user_info"""
    expect=db.QueryAsDict(sql)
    res=dbOperate
    assert expect==res

    在fixture中,在執行查詢語句前,db=DBOperate()至關於創建數據庫鏈接,可視爲配置過程(setup),而db.CloseDB()則至關於清理過程(teardown)過程,不管測試過程發生了什麼,清理過程均會被執行。fetch

5.3 使用--setup-show回溯fixture執行過程

    若是直接運行前面的測試,則看不到其fixture的執行過程,以下所示:

>>> pytest -v .\test_02.py
=================== test session starts =================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item

test_02.py::test_dbOperate PASSED                                   [100%]

===================== 1 passed in 0.07s ==================================

    若是但願看到其詳細的執行過程及執行的前後順序,可使用參數--setup-show,以下所示:

>>> pytest  --setup-show -v .\test_02.py
====================== test session starts ==================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item

test_02.py::test_dbOperate
        SETUP    F dbOperate
        test_02.py::test_dbOperate (fixtures used: dbOperate)PASSED
        TEARDOWN F dbOperate

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

    從上面的運行的輸出結果中能夠看到,真正的測試函數被夾在中間,pytest會將每個fixture的執行分紅setup和teardown兩部分。

fixture名稱前面F表明其做用範圍,F:表示函數級別,S:表示會話級別

5.4 使用fixture傳遞測試數據

    fixture很是適合存放測試數據,且能夠返回任何數據,示例以下所示:

import pytest

@pytest.fixture()
def sampleList():
    return [1,23,"a",{"a":1}]

def test_sampleList(sampleList):
    assert sampleList[1]==32

運行結果以下所示:

>>> pytest -v .\test_fixture.py
=========================== test session starts ==============================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item

test_fixture.py::test_sampleList FAILED                                         [100%]

================================= FAILURES ===========================================
_________________________test_sampleList ____________________________________________

sampleList = [1, 23, 'a', {'a': 1}]

    def test_sampleList(sampleList):
>       assert sampleList[1]==32
E       assert 23 == 32
E         +23
E         -32

test_fixture.py:8: AssertionError
===========================short test summary info =================================
FAILED test_fixture.py::test_sampleList - assert 23 == 32
=========================== 1 failed in 0.20s ======================================

    除了指明詳細的錯誤信息以外,pytest還給出了引發assert異常的函數參數值。fixture做爲測試函數的參數,也會被歸入測試報告中。
    上面的示例演示的是異常發生在測試函數中,那若是異常發生在fixture中,會怎麼樣?

import pytest

@pytest.fixture()
def sampleList():
    x=23
    assert x==32
    return x

def test_sampleList(sampleList):
    assert sampleList==32

運行結果以下所示:

>>> pytest -v .\test_fixture.py
======================= test session starts =============================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item

test_fixture.py::test_sampleList ERROR                                     [100%]

========================== ERRORS ==========================================
_________________ ERROR at setup of test_sampleList ________________________

    @pytest.fixture()
    def sampleList():
        x=23
>       assert x==32
E       assert 23 == 32
E         +23
E         -32

test_fixture.py:6: AssertionError
==================== short test summary info ================================
ERROR test_fixture.py::test_sampleList - assert 23 == 32
=========================== 1 error in 0.27s =================================

    在運行的輸出結果中,正肯定位到了fixture函數中發生assert異常的位置,其次test_sampleList並無被標記爲FAIL,而是被標記爲ERROR,這個區分很清楚,若是被標記爲FAIL,用戶就知道失敗發生在覈心函數中,而不是發生在測試依賴的fixture中。

5.5 使用多個fixture

    示例代碼以下所示:

1.DBOperate

import sqlite3
import os

class DBOperate:

    def __init__(self,dbPath=os.path.join(os.getcwd(),"db")):
        self.dbPath=dbPath
        self.connect=sqlite3.connect(self.dbPath)

    def Query(self,sql:str)->list:
        """傳統查詢語句"""
        queryResult = self.connect.cursor().execute(sql).fetchall()
        return queryResult if queryResult else []

    def QueryAsDict(self,sql:str)->dict:
        """調用該函數返回結果爲字典形式"""
        self.connect.row_factory=self.dictFactory
        cur=self.connect.cursor()
        queryResult=cur.execute(sql).fetchall()
        return queryResult if queryResult else {}

    def Insert(self,sql:str)->bool:
        insertRows=self.connect.cursor().execute(sql)
        self.connect.commit()
        return True if insertRows.rowcount else False

    def Update(self,sql:str)->bool:
        updateRows=self.connect.cursor().execute(sql)
        self.connect.commit()
        return  True if updateRows.rowcount else False


    def Delete(self,sql:str)->bool:
        delRows=self.connect.cursor().execute(sql)
        self.connect.commit()
        return True if delRows.rowcount else False

    def CloseDB(self):
        self.connect.cursor().close()
        self.connect.close()

    def dictFactory(self,cursor,row):
        """將sql查詢結果整理成字典形式"""
        d={}
        for index,col in enumerate(cursor.description):
            d[col[0]]=row[index]
        return d

2.conftest.py

import pytest
from DBOperate import DBOperate

@pytest.fixture()
def dbOperate():
    # setup:connect db
    db=DBOperate()
    yield
    # tearDown:close db
    db.CloseDB()

@pytest.fixture()
def mulQuerySqlA():
    return (
        "SELECT * FROM user_info",
        "SELECT * FROM case_info",
        "SELECT * FROM config_paras"
    )

@pytest.fixture()
def mulQuerySqlB():
    return (
        "SELECT * FROM user_info WHERE account in('admin')",
        "SELECT * FROM case_info WHERE ID in('TEST-1')",
        "SELECT * FROM config_paras WHERE accountMinChar==2",
        "SELECT * FROM report_info WHERE ID in('TEST-1')"
    )

@pytest.fixture()
def mulFixtureA(dbOperate,mulQuerySqlA):
    db = DBOperate()
    tmpList=[]
    for item in mulQuerySqlA:
        tmpList.append(db.QueryAsDict(item))
    return tmpList

@pytest.fixture()
def mulFixtureB(dbOperate,mulQuerySqlB):
    db = DBOperate()
    tmpList = []
    for item in mulQuerySqlB:
        tmpList.append(db.QueryAsDict(item))
    return tmpList

3.test_03.py

import pytest

def test_count(mulQuerySqlA):
    assert len(mulQuerySqlA)==3

運行結果以下所示:

>>> pytest -v --setup-show .\test_03.py
========================= test session starts ================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item

test_03.py::test_count
        SETUP    F mulQuerySqlA
        test_03.py::test_count (fixtures used: mulQuerySqlA)PASSED
        TEARDOWN F mulQuerySqlA

========================== 1 passed in 0.05s ==================================

    使用fixture的優點在於:用戶編寫的測試函數能夠只考慮核心的測試邏輯,而不須要考慮測試前的準備工做。

5.6 指定fixture做用範圍

    fixture有一個叫scope的可選參數,稱爲做用範圍,經常使用於控制fixture什麼時候執行配置和銷燬邏輯。@pytest.fixture()一般有4個可選值,分別爲functionclassmodulesession,默認爲function。各個scope的描述信息以下所示:

  • 1.scope="function"

    函數級別的fixture每一個測試函數僅運行一次,配置代碼在測試函數運行以前運行,清理代碼則在測試函數運行以後運行。

  • 2.scope="class"

    類級別的fixture每一個測試類僅運行一次,不管類中有多少個測試方法,均可以共享這個fixture.

  • 3.scope="moudle"

    模塊級別的fixture每一個模塊僅運行一次,不管模塊中有多少個測試函數、測試方法或其餘fixture均可以共享這個fixture

  • 4.scope="session"

    會話級別的fixture每一個會話僅運行一次,一次會話中,全部測試方法和測試函數均可以共享這個fixture。

    各個做用範圍的scope示例以下所示:

import pytest

@pytest.fixture(scope="function")
def funcScope():
    pass

@pytest.fixture(scope="module")
def moduleScope():
    pass

@pytest.fixture(scope="session")
def sessionScope():
    pass

@pytest.fixture(scope="class")
def classScope():
    pass

def test_A(sessionScope,moduleScope,funcScope):
    pass

@pytest.mark.usefixtures("classScope")
class TestSomething:
    def test_B(self):
        pass
    def test_C(self):
        pass

運行結果以下所示:

>>> pytest --setup-show -v .\test_scope.py
=================== test session starts ===================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 3 items

test_scope.py::test_A
SETUP    S sessionScope
    SETUP    M moduleScope
        SETUP    F funcScope
        test_scope.py::test_A (fixtures used: funcScope, moduleScope, sessionScope)PASSED
        TEARDOWN F funcScope
test_scope.py::TestSomething::test_B
      SETUP    C classScope
        test_scope.py::TestSomething::test_B (fixtures used: classScope)PASSED
test_scope.py::TestSomething::test_C
        test_scope.py::TestSomething::test_C (fixtures used: classScope)PASSED
      TEARDOWN C classScope
    TEARDOWN M moduleScope
TEARDOWN S sessionScope

=========================3 passed in 0.04s ===============================

    以上各字母表明瞭不一樣的scope級別,C(class)、M(module)、F(function)、S(Session)

fixture只能使用同級別或比本身更高級別的fixture。例如函數級別的fixture可使用同級別的fixture,也可使用類級別、模塊級別、會話級別的fixture,反之則不行。

5.7 使用usefixture指定fixture

    除在測試函數列表中指定fixture以外,也能夠用@pytest.mark.usefixtures("fixture1","fixture2")標記測試函數或類。這種標記方法對測試類很是適用。以下所示:

@pytest.fixture(scope="class")
def classScope():
    pass

@pytest.mark.usefixtures("classScope")
class TestSomething:
    def test_B(self):
        pass
    def test_C(self):
        pass

使用usefixtures和在測試方法中添加fixture參數,二者並沒有太大差異,惟一區別在於後者可使用fixture的返回值。

5.8 給fixture添加autouse選項

    以前所用到的fixture都是根據測試自己來命名或針對示例中的測試類使用usefixtures,也能夠經過指定autouse=True選項,使做用範圍內的測試函數都運行該fixture,這種方式很是適合須要屢次運行,但不依賴任何系統狀態或外部數據的代碼。示例代碼以下所示:

import pytest
import time

@pytest.fixture(autouse=True,scope="session")
def endSessionTimeScope():
    yield
    print(f"\nfinished {time.strftime('%Y-%m-%d %H:%M:%S')}")

@pytest.fixture(autouse=True)
def timeDeltaScope():
    startTime=time.time()
    yield
    endTime=time.time()
    print(f"\ntest duration:{round(endTime-startTime,3)}")

def test_A():
    time.sleep(2)

def test_B():
    time.sleep(5)

運行結果以下所示:

>>> pytest -v -s .\test_autouse.py
==================== test session starts =======================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 2 items

test_autouse.py::test_A PASSED
test duration:2.002

test_autouse.py::test_B PASSED
test duration:5.006

finished 2020-05-26 12:35:57

=============================2 passed in 7.13s ====================================

5.9 給fixture重命名

    fixture的名字一般顯示在使用它的測試或其餘fixture函數的參數列表上,通常會和fixture函數名保持一致。pytest也容許使用@pytest.fixture(name="fixtureName")對fixture重命名。示例以下所示:

import pytest

@pytest.fixture(name="Surpass")
def getData():
    return [1,2,3]

def test_getData(Surpass):
    assert Surpass==[1,2,3]

    在前面的示例中,使用fixture名字時,是用的函數名,而使用@pytest.fixture(name="Surpass")後,就至關於給fixture取了一別名。在調用fixture時,則可使用別名了。運行結果以下所示:

>>> pytest --setup-show .\test_renamefixture.py
=======================test session starts ===============================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item

test_renamefixture.py
        SETUP    F Surpass
        test_renamefixture.py::test_getData (fixtures used: Surpass).
        TEARDOWN F Surpass

=========================== 1 passed in 0.05s ===============================

    若是想找出重命名後的fixture定義,可使用pytest的選項--fixtures,並提供所在測試文件名。pytest可提供全部測試使用的fixture,包含重命名的,以下所示:

>>> pytest --fixtures .\test_renamefixture.py
========================test session starts =================================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 1 item
--------------------------- fixtures defined from conftest ----------------------------------
mulQuerySqlA
    conftest.py:14: no docstring available

mulQuerySqlB
    conftest.py:22: no docstring available

mulFixtureA
    conftest.py:32: no docstring available

mulFixtureB
    conftest.py:40: no docstring available

dbOperate
    conftest.py:5: no docstring available


------------------------- fixtures defined from test_renamefixture -------------------------
Surpass
    test_renamefixture.py:4: no docstring available

5.10 fixture參數化

    在4.7中已經介紹過測試的參數化,也能夠對fixture作參數化處理。下面來演示fixture參數化另外一個功能,以下所示:

import pytest

paras=((1,2),(3,5),(7,8),(10,-98))
parasIds=[f"{x},{y}" for x,y in paras]

def add(x:int,y:int)->int:
    return x+y
@pytest.fixture(params=paras,ids=parasIds)
def getParas(request):
    return request.param

def test_add(getParas):
    res=add(getParas[0],getParas[1])
    expect=getParas[0]+getParas[1]
    assert res==expect

    fixture參數列表中的request也是pytest內建的fixture之一,表明fixture的調用狀態。getParas邏輯很是簡單,僅以request.param作爲返回值供測試用,paras裏面有4個元素,所以須要被調用4次,運行結果以下所示:

>>> pytest -v .\test_fixtrueparamize.py
============================ test session starts ================================
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03
collected 4 items

test_fixtrueparamize.py::test_add[1,2] PASSED                               [ 25%]
test_fixtrueparamize.py::test_add[3,5] PASSED                               [ 50%]
test_fixtrueparamize.py::test_add[7,8] PASSED                               [ 75%]
test_fixtrueparamize.py::test_add[10,-98] PASSED                            [100%]

================================ 4 passed in 0.10s =====================================

原文地址:http://www.javashuo.com/article/p-yksgfrlk-nt.html

本文同步在微信訂閱號上發佈,如各位小夥伴們喜歡個人文章,也能夠關注個人微信訂閱號:woaitest,或掃描下面的二維碼添加關注:
MyQRCode.jpg

本站公眾號
   歡迎關注本站公眾號,獲取更多信息