pytest內置的fixture能夠大量簡化測試工做。如在處理臨時文件時,pytest內置的fixture能夠識別命令行參數、在多個測試會話間通訊、校驗輸出流、更改環境變量、審查錯誤報警等。內置fixture是對pytest核心功能的擴展。html
內置的tmpdir和tmpdir_factory負責在測試開始運行前建立臨時文件目錄,並在測試結束後刪除。其主要特性以下所示:node
示例代碼以下所示:python
import pytest def test_tmpDir(tmpdir): tmpfileA=tmpdir.join("testA.txt") tmpSubDir=tmpdir.mkdir("subDir") tmpfileB=tmpSubDir.join("testB.txt") tmpfileA.write("this is pytest tmp file A") tmpfileB.write("this is pytest tmp file B") assert tmpfileA.read()=="this is pytest tmp file A" assert tmpfileB.read()=="this is pytest tmp file B"
tmpdir的做用範圍是函數級別,因此只能針對測試函數使用tmpdir建立文件或目錄。若是fixture做用範圍高於函數級別(類、模塊、會話),則須要使用tmpdir_factory。tmpdir與tmpdir_factory相似,但提供的方法有一些不一樣,以下所示:json
import pytest def test_tmpDir(tmpdir_factory): baseTmpDir=tmpdir_factory.getbasetemp() print(f"\nbase temp dir is :{baseTmpDir}") tmpDir_factory=tmpdir_factory.mktemp("tempDir") tmpfileA=tmpDir_factory.join("testA.txt") tmpSubDir=tmpDir_factory.mkdir("subDir") tmpfileB=tmpSubDir.join("testB.txt") tmpfileA.write("this is pytest tmp file A") tmpfileB.write("this is pytest tmp file B") assert tmpfileA.read()=="this is pytest tmp file A" assert tmpfileB.read()=="this is pytest tmp file B"
getbasetemp()用於返回該會話使用的根目錄,pytest-NUM會隨着會話的增長而進行自增,pytest會記錄最近幾回會話使用的根目錄,更早的根目錄記錄則會被清理掉。另外也可在命令行指定臨時目錄,以下所示:微信
pytest --basetemp=dir
運行結果以下所示:session
>>> pytest -s -v .\test_01.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\Lesson04 collected 1 item test_01.py::test_tmpDir base temp dir is :C:\Users\Surpass\AppData\Local\Temp\pytest-of-Surpass\pytest-11 PASSED ========================= 1 passed in 0.12s ==================================
tmpdir_factory的做用範圍是會話級別的,tmpdir的做用範圍是函數級別的。若是須要模塊級別或類級別的做用範圍的目錄,該如何解決了?針對這種狀況,能夠利用tmpdir_factory再建立一個fixture。app
假設有一個測試模塊,其中有不少測試用例須要讀取一個JSON文件,則能夠在模塊自己或conftest.py中建立一個做用範圍爲模塊級別的fixture用於配置該誰的,示例以下所示:dom
conftest.py函數
import json import pytest @pytest.fixture(scope="module") def readJson(tmpdir_factory): jsonData={ "name":"Surpass", "age":28, "locate":"shangahi", "loveCity":{"shanghai":"shanghai", "wuhai":"hubei", "shenzheng":"guangdong" } } file=tmpdir_factory.mktemp("jsonTemp").join("tempJSON.json") with open(file,"w",encoding="utf8") as fo: json.dump(jsonData,fo,ensure_ascii=False) # print(f"base dir is {tmpdir_factory.getbasetemp()}") return file
test_02.py測試
import json def test_getData(readJson): with open(readJson,"r",encoding="utf8") as fo: data=json.load(fo) assert data.get("name")=="Surpass" def test_getLoveCity(readJson): with open(readJson,"r",encoding="utf8") as fo: data=json.load(fo) getCity=data.get("loveCity") for k,v in getCity.items(): assert len(v)>0
運行結果以下所示:
>>> 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\Lesson04 collected 2 items test_02.py::test_getData PASSED [ 50%] test_02.py::test_getLoveCity PASSED [100%] ========================== 2 passed in 0.08s ==================================
由於建立的fixture級別爲模塊級別,所以JSON只會被建立一次。
內置的pytestconfig能夠經過命令行參數、選項、配置文件、插件、運行目錄等方式來控制pytest。pytestconfig是request.config的快捷方式,在pytest中稱之爲」pytest配置對象「
爲了理解pytestconfig是如何工做,能夠查看如何添加一個自定義的命令行選項,而後在測試用例中讀取該選項。另外也能夠直接從pytestconfig裏讀取自定義的命令行選項,爲了讓pytest可以解析,還須要使用hook函數(hook函數是另外一種控制pytest的方法,在插件中頻繁使用)。示例以下所示:
pytestconfig\conftest.py
def pytest_addoption(parser): parser.addoption("--myopt",action="store_true",help="test boolean option") parser.addoption("--foo",action="store",default="Surpass",help="test stroe")
運行結果以下所示:
>>> pytest --help usage: pytest [options] [file_or_dir] [file_or_dir] [...] ... custom options: --myopt test boolean option --foo=FOO test stroe
下面來嘗試在測試用例中使用這些選項,以下所示:
pytestconfig\test_03.py
import pytest def test_myOption(pytestconfig): print(f"--foo {pytestconfig.getoption('foo')}") print(f"--myopt {pytestconfig.getoption('myopt')}")
運行結果以下所示:
>>> pytest -s -q .\test_03.py --foo Surpass --myopt False . 1 passed in 0.08s >>> pytest -s -q --myopt .\test_03.py --foo Surpass --myopt True . 1 passed in 0.02s >>> pytest -s -q --myopt --foo Surpass .\te st_03.py --foo Surpass --myopt True
由於pytestconfig是一個fixture,因此也能夠被其餘fixture使用。以下所示:
import pytest @pytest.fixture() def foo(pytestconfig): return pytestconfig.option.foo @pytest.fixture() def myopt(pytestconfig): return pytestconfig.option.myopt def test_fixtureForAddOption(foo,myopt): print(f"\nfoo -- {foo}") print(f"\nmyopt -- {myopt}")
運行結果以下所示:
>>> pytest -v -s .\test_option.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\Lesson04\pytestconfig collected 1 item test_option.py::test_fixtureForAddOption foo -- Surpass myopt -- False PASSED ======================= 1 passed in 0.14s ================================
除了使用pytestconfig自定義以外,也可使用內置的選項和pytest啓動時的信息,如目錄、參數等。如所示:
def test_pytestconfig(pytestconfig): print(f"args : {pytestconfig.args}") print(f"ini file is : {pytestconfig.inifile}") print(f"root dir is : {pytestconfig.rootdir}") print(f"invocation dir is :{pytestconfig.invocation_dir}") print(f"-q, --quiet {pytestconfig.getoption('--quiet')}") print(f"-l, --showlocals:{pytestconfig.getoption('showlocals')}") print(f"--tb=style: {pytestconfig.getoption('tbstyle')}")
運行結果以下所示:
>>> pytest -v -s .\test_option.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\Lesson04\pytestconfig collected 1 item test_option.py::test_pytestconfig args : ['.\\test_option.py'] ini file is : None root dir is : C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson04\pytestconfig invocation dir is :C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson04\pytestconfig -q, --quiet 1 -l, --showlocals:False --tb=style: auto PASSED ==========================1 passed in 0.07s =================================
一般狀況下,每一個測試用例彼此都是獨立的,互不影響。但有時,一個測試用例運行完成後,但願將其結果傳遞給下一個測試用例,這種狀況下,則須要使用pytest內置的cache。
cache的做用是存在一段測試會話信息,在下一段測試會話中使用。使用pytest內置的--last-failed和--failed-first標識能夠很好的展現cache功能。示例以下所示:
def test_A(): assert 1==1 def test_B(): assert 1==2
運行結果以下所示:
>>> pytest -v --tb=no .\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\Lesson04 collected 2 items test_04.py::test_A PASSED [ 50%] test_04.py::test_B FAILED [100%] ======================== 1 failed, 1 passed in 0.08s ========================
上面有一個測試用例運行失敗,再次使用--ff或--failed-first,則以前運行失敗的測試用例會首先被運行,而後才運行其餘的測試用例,以下所示:
>>> pytest -v --tb=no --ff .\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\Lesson04 collected 2 items run-last-failure: rerun previous 1 failure first test_04.py::test_B FAILED [ 50%] test_04.py::test_A PASSED [100%] ======================= 1 failed, 1 passed in 0.14s ===================
另外也可使用--lf或--last-failed僅運行上次運行失敗的測試用例,以下所示:
>>> pytest -v --tb=no --lf .\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\Lesson04 collected 1 item run-last-failure: rerun previous 1 failure test_04.py::test_B FAILED [100%] ========================1 failed in 0.07s =============================
pytest是如何存儲並優先調用的呢?咱們先來看看如下這個測試用例,以下所示:
import pytest from pytest import approx testData=[ #x,y,res (1,2,3), (2,4,6), (3,5,8), (-1,-2,0) ] @pytest.mark.parametrize("x,y,expect",testData) def test_add(x,y,expect): res=x+y assert res==approx(expect)
運行結果以下所示:
>>> pytest -v -q .\test_04.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\Lesson04 collected 4 items test_04.py ...F [100%] ================== FAILURES ======================================= ___________________ test_add[-1--2-0] _____________________________ x = -1, y = -2, expect = 0 @pytest.mark.parametrize("x,y,expect",testData) def test_add(x,y,expect): res=x+y > assert res==approx(expect) E assert -3 == 0 ± 1.0e-12 E + where 0 ± 1.0e-12 = approx(0) test_04.py:16: AssertionError =================== short test summary info ======================= FAILED test_04.py::test_add[-1--2-0] - assert -3 == 0 ± 1.0e-12 =================== 1 failed, 3 passed in 0.26s ===================
根據報錯提示信息,咱們一眼就能找到錯誤,那針對不是那麼好定位的問題的測試用例了,這個時候就須要使用--showlocals(簡寫-l)來調試失敗的測試用例。以下所示:
>>> pytest -q --lf -l .\test_04.py F [100%] ========================= FAILURES ============================= ________________________ test_add[-1--2-0] _____________________ x = -1, y = -2, expect = 0 @pytest.mark.parametrize("x,y,expect",testData) def test_add(x,y,expect): res=x+y > assert res==approx(expect) E assert -3 == 0 ± 1.0e-12 E + where 0 ± 1.0e-12 = approx(0) expect = 0 res = -3 x = -1 y = -2 test_04.py:16: AssertionError ======================== short test summary info ==================== FAILED test_04.py::test_add[-1--2-0] - assert -3 == 0 ± 1.0e-12 1 failed in 0.17s
經過以上信息,能夠很直觀看出問題所在位置,爲記住上次測試失敗的用例,pytest存儲了上一個測試會話中測試失敗的信息,可使用--cache-show標識來顯示存儲的信息。
>>> pytest --cache-show ======================= 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\Lesson04 cachedir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson04\.pytest_cache ----------------------------- cache values for '*' ------------------------ cache\lastfailed contains: {'pytestconfig/test_03.py::test_myOption': True, 'test_04.py::test_B': True, 'test_04.py::test_add[-1--2-0]': True} cache\nodeids contains: ['test_01.py::test_tmpDir', 'test_02.py::test_getLoveCity', 'test_02.py::test_getData', 'test_04.py::test_A', 'test_04.py::test_B', 'pytestconfig/test_03.py::test_myOption', 'pytestconfig/test_option.py::test_pytestconfig', 'test_04.py::test_add[1-2-3]', 'test_04.py::test_add[2-4-6]', 'test_04.py::test_add[3-5-8]', 'test_04.py::test_add[-1--2-0]'] cache\stepwise contains: [] ========================no tests ran in 0.03s ==============================
若是須要清空cache,能夠在測試會話以前,傳入--clear-cache標識便可,cache除了--lf和--ff兩個標識以外,還可使用其接口,以下所示:
cache.get(key,default) cache.set(key,value)
習慣上,鍵名以應用名字或插件名字開始,接着是 / ,而後是分隔開的鍵字符串。鍵值能夠是任何可轉化成JSON的東西,由於在cache目錄中是用JSON格式存儲的。
下面來建立一個fixture,記錄測試的耗時,並存儲到cache中,若是後面的測試耗時大於以前的2倍,就拋出超時異常。
import datetime import time import random import pytest @pytest.fixture(autouse=True) def checkDuration(request,cache): key="duration/"+request.node.nodeid.replace(":","_") startTime=datetime.datetime.now() yield endTime=datetime.datetime.now() duration=(endTime-startTime).total_seconds() lastDuration=cache.get(key,None) cache.set(key,duration) if lastDuration is not None: errorString="test duration over twice last duration" assert duration <= 2 * lastDuration,errorString @pytest.mark.parametrize("t",range(5)) def test_duration(t): time.sleep(random.randint(0,5))
nodeid是一個獨特的標識,即使在參數化測試中也能使用。按如下步驟運行測試用例
>>> pytest -q --cache-clear .\test_04.py ..... [100%] 5 passed in 10.14s >>> pytest -q --tb=line .\test_04.py .E....E [100%] ========================== ERRORS ======================================== _________________ ERROR at teardown of test_duration[0] __________________ assert 5.006229 <= (2 * 1.003045) E AssertionError: test duration over twice last duration _________________ ERROR at teardown of test_duration[4] ___________________ assert 4.149226 <= (2 * 1.005112) E AssertionError: test duration over twice last duration ================== short test summary info ================================ ERROR test_04.py::test_duration[0] - AssertionError: test duration over twice last duration ERROR test_04.py::test_duration[4] - AssertionError: test duration over twice last duration 5 passed, 2 errors in 14.50s >>> pytest -q --cache-show cachedir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson04\.pytest_cache ----------------------- cache values for '*' ----------------------------------------- cache\lastfailed contains: {'test_04.py::test_duration[0]': True, 'test_04.py::test_duration[4]': True} cache\nodeids contains: ['test_04.py::test_duration[0]', 'test_04.py::test_duration[1]', 'test_04.py::test_duration[2]', 'test_04.py::test_duration[3]', 'test_04.py::test_duration[4]'] cache\stepwise contains: [] duration\test_04.py__test_duration[0] contains: 5.006229 duration\test_04.py__test_duration[1] contains: 0.001998 duration\test_04.py__test_duration[2] contains: 1.006201 duration\test_04.py__test_duration[3] contains: 4.007687 duration\test_04.py__test_duration[4] contains: 4.149226 no tests ran in 0.03s
由於cache數據有前綴,能夠直接看見duration數據。
pytest內置的capsys主要有兩個功能
1.讀取stdout
def greeting(name): print(f"Hello,{name}") def test_greeting(capsys): greeting("Surpass") out,err=capsys.readouterr() assert "Hello,Surpass" in out assert err==""
運行結果以下所示:
>>> pytest -v .\test_05.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\Lesson04 collected 1 item test_05.py::test_greeting PASSED [100%] ====================== 1 passed in 0.08s ==================================
2.讀取stderr
import sys def greeting(name): print(f"Hello,{name}",file=sys.stderr) def test_greeting(capsys): greeting("Surpass") out,err=capsys.readouterr() assert "Hello,Surpass" in err assert out==""
運行結果以下所示:
>>> pytest -v .\test_05.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\Lesson04 collected 1 item test_05.py::test_greeting PASSED [100%] ============================ 1 passed in 0.11s ===========================
pytest一般會抓取測試用例及被測試代碼的輸出。並且是在所有測試會話結束後,抓取到的輸出纔會隨着失敗的測試顯示出來。--s參數能夠關閉該功能,在測試仍在運行時就把輸出直接發送到stdout,但有時僅須要其中的部分信息,則可使用capsys.disable(),能夠臨時讓輸出繞過默認的輸出捕獲機制,示例以下所示:
def test_capsysDisable(capsys): with capsys.disabled(): print("\nalways print this information") print("normal print,usually captured")
運行結果以下所示:
>>> pytest -q .\test_05.py always print this information . [100%] 1 passed in 0.02s >>> pytest -q -s .\test_05.py always print this information normal print,usually captured . 1 passed in 0.02s
無論有沒有捕獲到輸出,always print this information始終都會顯示,是由於其包含在capsys.disabled()的代碼塊中執行的。其餘的打印語句是正常命令,在傳入-s參數纔會顯示。
-s標識是--capture=no的簡寫,表示關閉輸出捕獲
monkey patch能夠在運行期間對類或模塊進行動態修改。在測試中,monkey patch經常使用於替換被測試代碼的部分運行環境或裝飾輸入依賴或輸出依賴替換成更容易測試的對象或函數。在pytest內置的monkey patch 容許單一環境中使用,並在測試結束後,不管結果是失敗或經過,全部修改都會復原。monkeypatch經常使用的函數以下所示:
setattr(target, name, value=<notset>, raising=True): # 設置屬性 delattr(target, name=<notset>, raising=True): # 刪除屬性 setitem(dic, name, value): # 設置字典中一個元素 delitem(dic, name, raising=True): # 刪除字典中一個元素 setenv(name, value, prepend=None): # 設置環境變量 delenv(name, raising=True): # 刪除環境變量 syspath_prepend(path) # 將path路徑添加到sys.path中 chdir(path) # 改變當前的工做路徑
爲更好理解monkeypatch的實際應用方式,咱們先來看看如下示例:
import os import json defaulData={ "name":"Surpass", "age":28, "locate":"shangahi", "loveCity":{"shanghai":"shanghai", "wuhai":"hubei", "shenzheng":"guangdong" } } def readJSON(): path=os.path.join(os.getcwd(),"surpass.json") with open(path,"r",encoding="utf8") as fo: data=json.load(fo) return data def writeJSON(data:str): path = os.path.join(os.getcwd(), "surpass.json") with open(path,"w",encoding="utf8") as fo: json.dump(data,fo,ensure_ascii=False,indent=4) def writeDefaultJSON(): writeJSON(defaulData)
writeDefaultJSON()既沒有參數也沒有返回值,該如何測試?仔細觀察函數,它會在當前目錄中保存一個JSON文件,那就能夠從側面來進行測試。一般比較直接的方法,運行代碼並檢查文件的生成狀況。以下所示:
def test_writeDefaultJSON(): writeDefaultJSON() expectd=defaulData actual=readJSON() assert expectd==actual
上面這種方法雖然能夠進行測試,但卻覆蓋了原有文件內容。函數裏面所傳遞的路徑爲當前目錄,那若是將目錄換成臨時目錄了,示例以下所示:
def test_writeDefaultJSONChangeDir(tmpdir,monkeypatch): tmpDir=tmpdir.mkdir("TestDir") monkeypatch.chdir(tmpDir) writeDefaultJSON() expectd=defaulData actual=readJSON() assert expectd==actual
以上這種雖然解決了目錄的問題,那若是測試過程,須要修改數據,又該如何,示例以下所示:
def test_writeDefaultJSONChangeDir(tmpdir,monkeypatch): tmpDir=tmpdir.mkdir("TestDir") monkeypatch.chdir(tmpDir) # 保存默認數據 writeDefaultJSON() copyData=deepcopy(defaulData) # 增長項 monkeypatch.setitem(defaulData,"hometown","hubei") monkeypatch.setitem(defaulData,"company",["Surpassme","Surmount"]) addItemData=defaulData # 再次保存數據 writeDefaultJSON() # 獲取保存的數據 actual=readJSON() assert addItemData==actual assert copyData!=actual
由於默認的數據是字典格式的,因此可使用setitem來進行添加鍵值對。
內置的recwarn能夠用來檢查待測代碼產生的警告信息。在Python中,咱們能夠添加警告信息,很像斷言,但不阻止程序運行。假如在一份代碼,想要中止支持一個已通過時的函數,則能夠在代碼中設置警告信息,示例以下所示:
import warnings import pytest def depricateFunc(): warnings.warn("This function is not support after 3.8 version",DeprecationWarning) def test_depricateFunc(recwarn): depricateFunc() assert len(recwarn)==1 warnInfo=recwarn.pop() assert warnInfo.category==DeprecationWarning assert str(warnInfo.message) == "This function is not support after 3.8 version"
recwarn的值就是一個警告信息列表,列表中的每一個警告信息都有4個屬性category、message、filename、lineno。警告信息在測試開始後收集,若是待測的警告信息在最後,則能夠在信息收集前使用recwarn.clear()清除不須要的內容。
除recwarn,還可使用pytest.warns()來檢查警告信息。示例以下所示:
import warnings import pytest def depricateFunc(): warnings.warn("This function is not support after 3.8 version",DeprecationWarning) def test_depricateFunc(): with pytest.warns(None) as warnInfo: depricateFunc() assert len(warnInfo)==1 w=warnInfo.pop() assert w.category==DeprecationWarning assert str(w.message) == "This function is not support after 3.8 version"
原文地址:http://www.javashuo.com/article/p-uuaqdixc-nt.html
本文同步在微信訂閱號上發佈,如各位小夥伴們喜歡個人文章,也能夠關注個人微信訂閱號:woaitest,或掃描下面的二維碼添加關注: