使用pytest編寫測試時,若須要傳遞測試失敗信息,能夠直接使用Pytho自帶的assert關鍵字。pytest與其餘測試框架如unittest的區別以下所示:html
pytest | unittest |
---|---|
assert something | assertTrue(something) |
assert a==b | assertEqual(a,b) |
assert a<=b | assertLessEqual(a,b) |
... | ... |
pytest中assert主要有如下特色node
import pytest def addItemToList(item): tmpList=[] tmpList.append(item) return tmpList def test_addItemToList(): t1=addItemToList("a") t2=addItemToList("b") assert t1==t2
運行結果以下所示:python
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_01.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\Lesson02 collected 1 item test_01.py F [100%] ================================== FAILURES ====================================== ___________________________test_addItemToList ____________________________________ def test_addItemToList(): t1=addItemToList("a") t2=addItemToList("b") > assert t1==t2 E AssertionError: assert ['a'] == ['b'] E At index 0 diff: 'a' != 'b' E Use -v to get the full diff test_01.py:12: AssertionError =================================== short test summary info ========================= FAILED test_01.py::test_addItemToList - AssertionError: assert ['a'] == ['b'] ====================================1 failed in 0.29s ==============================
在上面的運行結果中,失敗的測試用例在行首會有一個 > 來標識。以E開頭的行是pytest提供的額外斷定信息,用於幫助瞭解異常的具體信息。數組
在Python後來的版本,增長了函數參數註解功能,即在定義一個參數後,後面可直接聲明參數的數據類型,方便其餘人員知道如何調用這個函數,以下所示:微信
def add(x:int,y:int)->int: return x+y
爲確保像這樣的函數,在發生類型錯誤時,能夠拋出異常,可使用with pytest.raises(< expected exception >),以下所示:session
def add(x:int,y:int)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)): raise TypeError("args must be int") else: return x+y def test_add(): with pytest.raises(TypeError): add("1","2")
運行結果以下所示:app
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_01.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\Lesson02 collected 1 item test_01.py . [100%] =================================1 passed in 0.08s ===============================
測試用例中test_add()有with pytest.raises(TypeError)聲明,則意味着不管with中的內容是什麼,都至少會產生TypeError異常。若是測試經過,則說明確實發生了預期的TypeError異常,若是拋出的是其餘類型的異常,則與預期不一致,測試失敗。框架
在上面的測試用例中,僅檢驗了傳參數據的類型異常,也能夠檢查值異常,好比在檢驗一個參數的數據類型以後,也能夠再檢驗其內容,爲校驗異常消息是否符合預期,能夠增長as exInfo語句獲得異常消息的值,再進行校驗,示例以下所示:函數
def add(x:int,y:int,operator:str)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") def test_add(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo
運行結果以下所示:測試
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_01.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\Lesson02 collected 1 item test_01.py . [100%] ============================1 passed in 0.13s ================================
pytest提供了標記機制,容許使用marker對測試函數進行標記,一個測試函數能夠有多個marker,一個marker也能夠用來標記多個測試函數。
對測試函數進行標記,一般使用的場景爲冒煙測試,通常狀況下冒煙測試不須要跑所有的測試用例,只須要選擇關鍵的點進行測試,這個時候只跑被標記爲冒煙測試的測試用例,會省不少時間。
示例以下所示:
import pytest def add(x:int,y:int,operator:str)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.testValueError @pytest.mark.smoke def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo @pytest.mark.testTypeErrorTest @pytest.mark.smoke def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
如今只須要在命令中指定-m markerName,就能夠運行指定的測試用例,以下所示:
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -m smoke .\test_02.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\Lesson02 collected 2 items test_02.py .. [100%] ============================ 2 passed, 4 warnings in 0.12s ==================================== PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -m testValueError .\test_02.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\Lesson02 collected 2 items / 1 deselected / 1 selected test_02.py . [100%] ========================= 1 passed, 1 deselected, 4 warnings in 0.04s ===========================
針對上面示例,能夠得出如下結論
1.pytest.mark.markName:markName由用戶自行定義,並不是pytest內置
2.-m:後面也可使用表達式,能夠在標記之間添加and、or、not等關鍵字,以下所示:
pytest -m "testValueError and testTypeError" .\test_02.py pytest -m "testValueError and not testTypeError" .\test_02.py
pytest內置了一些標記,如skip、skipif、xfail。其中skip、skipif容許跳過不但願運行的測試。示例以下所示:
1.要直接跳過某一個測試函數,可使用pytest.mark.skip()裝飾器便可
import pytest def add(x:int,y:int,operator:str)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.testValueError @pytest.mark.smoke def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo @pytest.mark.skip(reason="skip this case") def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
運行結果以下所示:
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> 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\Lesson02 collected 2 items test_02.py::test_addValueError PASSED [ 50%] test_02.py::test_addTypeError SKIPPED [100%]
2.添加跳過的緣由和條件,可使用pytest.mark.skipif()裝飾器便可
若是有一些測試,只有在知足特定條件的狀況下,才被跳過,這時候則可使用pytest.mark.skipif(),示例以下所示:
import pytest def add(x:int,y:int,operator:str)->int: """this is sample case""" if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.testValueError @pytest.mark.smoke def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo @pytest.mark.skipif(add.__doc__=="this is sample case",reason="skip this case") def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
運行結果以下所示:
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\Lesson02 collected 2 items test_02.py::test_addValueError PASSED [ 50%] test_02.py::test_addTypeError SKIPPED [100%]
儘管跳過的緣由不是必須寫,但仍是建議在實際項目儘量的寫上,方便知道跳過的緣由,若是想知道跳過的詳細緣由,可以使用參數 -rs
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -rs -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\Lesson02 collected 2 items test_02.py::test_addValueError PASSED [ 50%] test_02.py::test_addTypeError SKIPPED [100%] ==================================short test summary info ================================= SKIPPED [1] test_02.py:23: skip this case ==========================1 passed, 1 skipped, 2 warnings in 0.04s ========================
使用skip和skipif,在知足條件的下,會直接跳過,而不會執行。使用xfail標記,則是預期運行失敗,以下所示:
import pytest def add(x:int,y:int,operator:str)->int: """this is sample case""" if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.xfail def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo!= exInfo @pytest.mark.xfail(add.__doc__=="this is sample case",reason="skip this case") def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
運行結果以下所示:
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_02.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\Lesson02 collected 2 items test_02.py xX [100%] ======================= 1 xfailed, 1 xpassed in 0.19s ===============================
對於標記爲xfail,但實際運行結果爲XPASS的測試,能夠在pytest配置中強制指定爲FAIL,在pytest.ini文件按以下修改便可:
[pytest] xfail_strict=true
前面主要介紹對測試函數進行標記及如何運行。而運行測試子集能夠按目錄、文件、類中的測試,還能夠選擇運行某一個測試用例(可能在文件中,也能夠在類中)**。
運行單個目錄下的全部測試,以目錄作爲參數便可,以下所示:
pytest --tb=no .\PytestStudy\
運行單個測試文件/模塊,以路徑名加文件名作爲參數便可,以下所示:
pytest --tb=no .\PytestStudy\Lesson02\test_01.py
運行單個測試函數,只須要在文件名後面添加::符號和函數名便可,以下所示:
pytest .\test_02.py::test_addTypeError
測試類用於將某些類似的測試函數組合在一塊兒,來看看如下示例:
import pytest class Person: _age=28 _name="Surpass" def __init__(self,age,name): self._name=name self._age=age @classmethod def getAge(cls): if not isinstance(cls._age,(int,)): raise TypeError("age must be integer") else: return cls._age @classmethod def getName(cls): if not isinstance(cls._name,(str,)): raise TypeError("name must be string") else: return cls._name class TestPerson: def test_getAge(self): with pytest.raises(TypeError): Person.getAge("28") def test_getName(self): with pytest.raises(TypeError): Person.getName(123)
以上測試類中的方法都是測試Person中的方法,所以能夠放在一個測試類中,要運行該類,能夠在文件名後面添加::符號和類名便可,與運行單個測試函數相似,以下所示:
pytest .\test_03.py::TestPerson -v ========================== 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\Lesson02 collected 2 items test_03.py::TestPerson::test_getAge PASSED [ 50%] test_03.py::TestPerson::test_getName PASSED [100%] ============================= 2 passed in 0.04s ============================================
若是不但願運行測試類中的全部測試方法,能夠指定運行的測試方法名即,能夠在文件名後面添加::類名::類中方法名便可,以下所示:
>>> pytest -v .\test_03.py::TestPerson::test_getName =========================== 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\Lesson02 collected 1 item test_03.py::TestPerson::test_getName PASSED [100%] ============================= 1 passed in 0.04s ===============================
-k選項容許用一個表達式指定須要運行的測試,表達式能夠匹配測試名或其子串,表達式中也能夠包含and、or、not等。
例如想運行目錄中,全部文件中測試函數名包含_add的測試函數,可按以下方式進行操做:
>>> pytest -v -k _add ======================== 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\Lesson02 collected 5 items / 2 deselected / 3 selected test_01.py::test_add PASSED [ 33%] test_02.py::test_addValueError XFAIL [ 66%] test_02.py::test_addTypeError XPASS [100%] ==================== 1 passed, 2 deselected, 1 xfailed, 1 xpassed in 0.14s ====================
向函數傳值並校驗其輸出是軟件測試的經常使用手段,但大部分狀況下,僅使用一組數據是沒法充分測試函數功能。參數化測試容許傳遞多組數據,一旦發現測試失敗,pytest會及時拋出信息。
要使用參數化測試,須要使用裝飾器pytest.mark.parametrize(args,argsvaules)來傳遞批量的參數。示例以下所示:
1.方式一
import pytest def add(x:int,y:int)->int: return x+y @pytest.mark.parametrize("paras",[(1,2),(3,5),(7,8),(10,-98)]) def test_add(paras): res=add(paras[0],paras[1]) expect=paras[0]+paras[1] assert res==expect
運行結果以下所示:
>>> pytest -v .\test_04.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\Lesson02 collected 4 items test_04.py::test_add[paras0] PASSED [ 25%] test_04.py::test_add[paras1] PASSED [ 50%] test_04.py::test_add[paras2] PASSED [ 75%] test_04.py::test_add[paras3] PASSED [100%] =====================4 passed in 0.10s ====================================
在parametrize()中第一個參數字符串列表(paras),第二個參數是一個值列表,pytest會輪流對每一個paras作測試,並分別報告每一個測試用例的結果。
parametrize()函數工做正常,那若是把paras替換爲鍵值對形式了,可否達到一樣的效果,以下所示:
2.方式二
import pytest def add(x:int,y:int)->int: return x+y @pytest.mark.parametrize(("x","y"),[(1,2),(3,5),(7,8),(10,-98)]) def test_add(x,y): res=add(x,y) expect=x+y assert res==expect
運行結果以下所示:
>>> pytest -v .\test_04.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\Lesson02 collected 4 items test_04.py::test_add[1-2] PASSED [ 25%] test_04.py::test_add[3-5] PASSED [ 50%] test_04.py::test_add[7-8] PASSED [ 75%] test_04.py::test_add[10--98] PASSED [100%] ======================4 passed in 0.16s =============================
若是傳入的參數具備標識性,則在輸出結果中也一樣具有可標識性,加強可讀性。也可使用完整的測試標識(pytest術語爲node),若是標識符中包含空格,則須要使用引號。從新運行指定的測試以下所示:
>>> pytest -v test_04.py::test_add[1-2] ================================ 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\Lesson02 collected 1 item test_04.py::test_add[1-2] PASSED [100%] ========================== 1 passed in 0.04s ===================================
3.方式三
除了以上兩種方式以外,還可使用以下方式進行參數化,以下所示:
import pytest def add(x:int,y:int)->int: return x+y paras=((1,2),(3,5),(7,8),(10,-98)) @pytest.mark.parametrize("p",paras) def test_add(p): res=add(p[0],p[1]) expect=p[0]+p[1] assert res==expect
運行結果以下所示
>>> pytest -v test_04.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\Lesson02 collected 4 items test_04.py::test_add[p0] PASSED [ 25%] test_04.py::test_add[p1] PASSED [ 50%] test_04.py::test_add[p2] PASSED [ 75%] test_04.py::test_add[p3] PASSED [100%] ==============================4 passed in 0.14s ======================================
1.方式一
以上幾種雖然能夠達到參數化的目的,但可讀性不太友好,爲改善可讀性,能夠爲parametrize()引入一個額外的參數ids,使列表中每一個元素都被標識。ids是一個字符串列表和數據對象列表和長度一致。以下所示:
import pytest def add(x:int,y:int)->int: return x+y paras=((1,2),(3,5),(7,8),(10,-98)) parasIds=[f"{x},{y}" for x,y in paras] @pytest.mark.parametrize("p",paras,ids=parasIds) def test_add(p): res=add(p[0],p[1]) expect=p[0]+p[1] assert res==expect
運行結果以下所示
>>> pytest -v test_04.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\Lesson02 collected 4 items test_04.py::test_add[1,2] PASSED [ 25%] test_04.py::test_add[3,5] PASSED [ 50%] test_04.py::test_add[7,8] PASSED [ 75%] test_04.py::test_add[10,-98] PASSED 100%] =================================== 4 passed in 0.11s ============================
parametrize()不只適於通常的測試函數,也能夠適用類,在用於類中,則數據集會被傳遞給該類的全部方法,以下所示:
import pytest paras=((1,2),(3,5),(7,8),(10,-98)) parasIds=[f"{x},{y}" for x,y in paras] class Oper: _x=0 _y=0 def __int__(self,x,y): self._x=x self._y=y @classmethod def add(cls,x,y): return x+y @classmethod def sub(cls,x,y): return x-y @pytest.mark.parametrize("p",paras,ids=parasIds) class TestOper: def test_add(self,p): res=Oper.add(p[0],p[1]) expect=p[0]+p[1] assert res==expect def test_sub(self,p): res = Oper.sub(p[0], p[1]) expect = p[0] - p[1] assert res == expect
運行結果以下所示
>>> pytest -v test_04.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\Lesson02 collected 8 items test_04.py::TestOper::test_add[1,2] PASSED [ 12%] test_04.py::TestOper::test_add[3,5] PASSED [ 25%] test_04.py::TestOper::test_add[7,8] PASSED [ 37%] test_04.py::TestOper::test_add[10,-98] PASSED [ 50%] test_04.py::TestOper::test_sub[1,2] PASSED [ 62%] test_04.py::TestOper::test_sub[3,5] PASSED [ 75%] test_04.py::TestOper::test_sub[7,8] PASSED [ 87%] test_04.py::TestOper::test_sub[10,-98] PASSED [100%] ============================ 8 passed in 0.13s ================================
2.方式二
在給@pytest.mark.parametrize()裝飾器傳入參數列表時,還能夠在參數值中定義一個id作爲標識,其語法格式以下所示:
@pytest.mark.parametrize(<value>,id="id")
示例以下所示:
import pytest class Oper: _x=0 _y=0 def __int__(self,x,y): self._x=x self._y=y @classmethod def add(cls,x,y): return x+y @classmethod def sub(cls,x,y): return x-y @pytest.mark.parametrize("p",[pytest.param((1,2),id="id-1"),pytest.param((3,5),id="id-2"), pytest.param((7,8),id="id-3"),pytest.param((10,-98),id="id-4")]) class TestOper: def test_add(self,p): res=Oper.add(p[0],p[1]) expect=p[0]+p[1] assert res==expect def test_sub(self,p): res = Oper.sub(p[0], p[1]) expect = p[0] - p[1] assert res == expect
運行結果以下所示
>>> pytest -v test_04.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\Lesson02 collected 8 items test_04.py::TestOper::test_add[id-1] PASSED [ 12%] test_04.py::TestOper::test_add[id-2] PASSED [ 25%] test_04.py::TestOper::test_add[id-3] PASSED [ 37%] test_04.py::TestOper::test_add[id-4] PASSED [ 50%] test_04.py::TestOper::test_sub[id-1] PASSED [ 62%] test_04.py::TestOper::test_sub[id-2] PASSED [ 75%] test_04.py::TestOper::test_sub[id-3] PASSED [ 87%] test_04.py::TestOper::test_sub[id-4] PASSED [100%] =============================== 8 passed in 0.19s =============================
在id不能被參數批量生成,須要自定義時,這個方法很是適用。
原文地址:http://www.javashuo.com/article/p-qreunrvh-nn.html
本文同步在微信訂閱號上發佈,如各位小夥伴們喜歡個人文章,也能夠關注個人微信訂閱號:woaitest,或掃描下面的二維碼添加關注: