本文節(jié)選自霍格沃玆測試學(xué)院內(nèi)部教材,進(jìn)階學(xué)習(xí)冲粤。
在上一篇文章中分享了 pytest 的基本用法袜茧,本文進(jìn)一步介紹 pytest 的其他實(shí)用特性和進(jìn)階技巧。
pytest fixtures
pytest 中可以使用 @pytest.fixture 裝飾器來裝飾一個(gè)方法倍踪,被裝飾方法的方法名可以作為一個(gè)參數(shù)傳入到測試方法中系宫。可以使用這種方式來完成測試之前的初始化建车,也可以返回?cái)?shù)據(jù)給測試函數(shù)扩借。
將 fixture 作為函數(shù)參數(shù)
通常使用 setup 和 teardown 來進(jìn)行資源的初始化。如果有這樣一個(gè)場景缤至,測試用例 1 需要依賴登錄功能潮罪,測試用例 2 不需要登錄功能,測試用例 3 需要登錄功能凄杯。這種場景 setup错洁,teardown 無法實(shí)現(xiàn),可以使用 pytest fixture 功能戒突,在方法前面加個(gè) @pytest.fixture 裝飾器屯碴,加了這個(gè)裝飾器的方法可以以參數(shù)的形式傳入到方法里面執(zhí)行。
例如在登錄的方法膊存,加上 @pytest.fixture 這個(gè)裝飾器后导而,將這個(gè)用例方法名以參數(shù)的形式傳到方法里,這個(gè)方法就會先執(zhí)行這個(gè)登錄方法隔崎,再去執(zhí)行自身的用例步驟今艺,如果沒有傳入這個(gè)登錄方法,就不執(zhí)行登錄操作爵卒,直接執(zhí)行已有的步驟虚缎。
創(chuàng)建一個(gè)文件名為“test_fixture.py”,代碼如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-import pytest@pytest.fixture()def login(): print("這是個(gè)登錄方法") return ('tom','123')@pytest.fixture()def operate(): print("登錄后的操作")def test_case1(login,operate): print(login) print("test_case1钓株,需要登錄")def test_case2(): print("test_case2实牡,不需要登錄 ")def test_case3(login): print(login) print("test_case3,需要登錄")
在上面的代碼中轴合,測試用例 test_case1 和 test_case3 分別增加了 login 方法名作為參數(shù)创坞,pytest 會發(fā)現(xiàn)并調(diào)用 @pytest.fixture 標(biāo)記的 login 功能,運(yùn)行測試結(jié)果如下:
plugins: html-2.0.1, rerunfailures-8.0, xdist-1.31.0, \ordering-0.6, forked-1.1.3, allure-pytest-2.8.11, metadata-1.8.0collecting ... collected 3 itemstest_fixture.py::test_case1 這是個(gè)登錄方法登錄后的操作PASSED [ 33%]('tom', '123')test_case1受葛,需要登錄test_fixture.py::test_case2 PASSED \[ 66%]test_case2题涨,不需要登錄 test_fixture.py::test_case3 這是個(gè)登錄方法PASSED [100%]('tom', '123')test_case3偎谁,需要登錄============================== 3 passed in 0.02s ===============================Process finished with exit code 0
從上面的結(jié)果可以看出,test_case1 和 test_case3 運(yùn)行之前執(zhí)行了 login 方法纲堵,test_case2 沒有執(zhí)行這個(gè)方法巡雨。
指定范圍內(nèi)共享
fixture 里面有一個(gè)參數(shù) scope,通過 scope 可以控制 fixture 的作用范圍婉支,根據(jù)作用范圍大小劃分:session> module> class> function鸯隅,具體作用范圍如下:
- function 函數(shù)或者方法級別都會被調(diào)用
- class 類級別調(diào)用一次
- module 模塊級別調(diào)用一次
- session 是多個(gè)文件調(diào)用一次(可以跨.py文件調(diào)用,每個(gè).py文件就是module)
例如整個(gè)模塊有多條測試用例向挖,需要在全部用例執(zhí)行之前打開瀏覽器蝌以,全部執(zhí)行完之后去關(guān)閉瀏覽器,打開和關(guān)閉操作只執(zhí)行一次何之,如果每次都重新執(zhí)行打開操作跟畅,會非常占用系統(tǒng)資源。這種場景除了setup_module,teardown_module 可以實(shí)現(xiàn),還可以通過設(shè)置模塊級別的 fixture 裝飾器(@pytest.fixture(scope="module"))來實(shí)現(xiàn)溶推。
scope='module'
fixture 參數(shù) scope='module'徊件,module 作用是整個(gè)模塊都會生效。
創(chuàng)建文件名為 test_fixture_scope.py蒜危,代碼如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-import pytest# 作用域:module是在模塊之前執(zhí)行虱痕, 模塊之后執(zhí)行@pytest.fixture(scope="module")def open(): print("打開瀏覽器") yield print("執(zhí)行teardown !") print("最后關(guān)閉瀏覽器")@pytest.mark.usefixtures("open")def test_search1(): print("test_search1") raise NameError passdef test_search2(open): print("test_search2") passdef test_search3(open): print("test_search3") pass
代碼解析:
@pytest.fixture() 如果不寫參數(shù),參數(shù)默認(rèn) scope='function'辐赞。當(dāng) scope='module' 時(shí)部翘,在當(dāng)前 .py 腳本里面所有的用例開始前只執(zhí)行一次。scope 巧妙與 yield 組合使用响委,相當(dāng)于 setup 和 teardown 方法新思。還可以使用 @pytest.mark.usefixtures 裝飾器,傳入前置函數(shù)名作為參數(shù)赘风。
運(yùn)行結(jié)果如下:
plugins: html-2.0.1, rerunfailures-8.0, \xdist-1.31.0, ordering-0.6, forked-1.1.3,\ allure-pytest-2.8.11, metadata-1.8.0collecting ... collected 3 itemstest_fixture_yield.py::test_search1 打開瀏覽器FAILED [ 33%]test_search1test_fixture_yield.py:13 (test_search1)open = None def test_search1(open): print("test_search1")> raise NameErrorE NameErrortest_fixture_yield.py:16: NameErrortest_fixture_yield.py::test_search2 PASSED \[ 66%]test_search2test_fixture_yield.py::test_search3 PASSED \[100%]test_search3執(zhí)行teardown !最后關(guān)閉瀏覽器...open = None def test_search1(open): print("test_search1")> raise NameErrorE NameErrortest_fixture_yield.py:16: NameError------ Captured stdout setup --------打開瀏覽器----- Captured stdout call -----test_search1===== 1 failed, 2 passed in 0.06s =====Process finished with exit code 0
從上面運(yùn)行結(jié)果可以看出夹囚,scope="module" 與 yield 結(jié)合,相當(dāng)于 setup_module 和 teardown_module 方法邀窃。整個(gè)模塊運(yùn)行之前調(diào)用了 open()方法中 yield 前面的打印輸出“打開瀏覽器”荸哟,整個(gè)運(yùn)行之后調(diào)用了 yield 后面的打印語句“執(zhí)行 teardown !”與“關(guān)閉瀏覽器”。yield 來喚醒 teardown 的執(zhí)行瞬捕,如果用例出現(xiàn)異常鞍历,不影響 yield 后面的 teardown 執(zhí)行∩轿觯可以使用 @pytest.mark.usefixtures 裝飾器來進(jìn)行方法的傳入。
conftest.py 文件
fixture scope 為 session 級別是可以跨 .py 模塊調(diào)用的掏父,也就是當(dāng)我們有多個(gè) .py 文件的用例時(shí)笋轨,如果多個(gè)用例只需調(diào)用一次 fixture,可以將 scope='session',并且寫到 conftest.py 文件里爵政。寫到 conftest.py 文件可以全局調(diào)用這里面的方法仅讽。使用的時(shí)候不需要導(dǎo)入 conftest.py 這個(gè)文件。使用 conftest.py 的規(guī)則:
- conftest.py 這個(gè)文件名是固定的钾挟,不可以更改洁灵。
- conftest.py 與運(yùn)行用例在同一個(gè)包下,并且該包中有 init.py 文件
- 使用的時(shí)候不需要導(dǎo)入 conftest.py掺出,pytest 會自動識別到這個(gè)文件
- 放到項(xiàng)目的根目錄下可以全局調(diào)用徽千,放到某個(gè) package 下,就在這個(gè) package 內(nèi)有效汤锨。
案例
在運(yùn)行整個(gè)項(xiàng)目下的所有的用例双抽,只執(zhí)行一次打開瀏覽器。執(zhí)行完所有的用例之后再執(zhí)行關(guān)閉瀏覽器闲礼,可以在這個(gè)項(xiàng)目下創(chuàng)建一個(gè) conftest.py 文件牍汹,將打開瀏覽器操作的方法放在這個(gè)文件下,并添加一個(gè)裝飾器 @pytest.fixture(scope="session")柬泽,就能夠?qū)崿F(xiàn)整個(gè)項(xiàng)目所有測試用例的瀏覽器復(fù)用慎菲,案例目錄結(jié)構(gòu)如下:
創(chuàng)建目錄 test_scope,并在目錄下創(chuàng)建三個(gè)文件 conftest.py锨并,test_scope1.py 和 test_scope2.py露该。
conftest.py 文件定義了公共方法,pytest 會自動讀取 conftest.py 定義的方法琳疏,代碼如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-import pytest@pytest.fixture(scope="session")def open(): print("打開瀏覽器") yield print("執(zhí)行teardown !") print("最后關(guān)閉瀏覽器")
創(chuàng)建 test_scope1.py 文件有决,代碼如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-import pytestdef test_search1(open): print("test_search1") passdef test_search2(open): print("test_search2") passdef test_search3(open): print("test_search3") passif __name__ == '__main__': pytest.main()
創(chuàng)建文件“test_scope2.py”,代碼如下:
#!/usr/bin/env python# -*- coding: utf-8 -*-class TestFunc(): def test_case1(self): print("test_case1空盼,需要登錄") def test_case2(self): print("test_case2书幕,不需要登錄 ") def test_case3(self): print("test_case3,需要登錄")
打開 cmd揽趾,進(jìn)入目錄 test_scope/台汇,執(zhí)行如下命令:
pytest -v -s
或者
pytest -v -s test_scope1.py test_scope2.py
執(zhí)行結(jié)果如下:
省略...collected 6 items test_scope1.py::test_search1 打開瀏覽器test_search1PASSEDtest_scope1.py::test_search2 test_search2PASSEDtest_scope1.py::test_search3 test_search3PASSEDtest_scope2.py::TestFunc::test_case1 test_case1,需要登錄PASSEDtest_scope2.py::TestFunc::test_case2 test_case2篱瞎,不需要登錄 PASSEDtest_scope2.py::TestFunc::test_case3 test_case3苟呐,需要登錄PASSED執(zhí)行teardown !最后關(guān)閉瀏覽器省略后面打印結(jié)果...
執(zhí)行過程中 pytest 會自動識別當(dāng)前目錄的 conftest.py,不需要導(dǎo)入直接引用里面的方法配置俐筋。應(yīng)用到整個(gè)目錄下的所有調(diào)用這里面的方法中執(zhí)行牵素。conftest.py 與運(yùn)行的用例要在同一個(gè) pakage 下,并且這個(gè)包下有 init.py 文件
自動執(zhí)行 fixture
如果每條測試用例都需要添加 fixture 功能澄者,則需要在每一要用例方法里面?zhèn)魅脒@個(gè)fixture的名字笆呆,這里就可以在裝飾器里面添加一個(gè)參數(shù) autouse='true'请琳,它會自動應(yīng)用到所有的測試方法中,只是這里沒有辦法返回值給測試用例赠幕。
使用方法俄精,在方法前面加上裝飾器,如下:
@pytest.fixture(autouse="true")def myfixture(): print("this is my fixture")
@pytest.fixture 里設(shè)置 autouse 參數(shù)值為 true(默認(rèn) false)榕堰,每個(gè)測試函數(shù)都會自動調(diào)用這個(gè)前置函數(shù)竖慧。
創(chuàng)建文件名為“test_autouse.py”,代碼如下:
# coding=utf-8import pytest@pytest.fixture(autouse="true")def myfixture(): print("this is my fixture")class TestAutoUse: def test_one(self): print("執(zhí)行test_one") assert 1 + 2 == 3 def test_two(self): print("執(zhí)行test_two") assert 1 == 1 def test_three(self): print("執(zhí)行test_three") assert 1 + 1 == 2
執(zhí)行上面這個(gè)測試文件,結(jié)果如下:
...test_a.py::TestAutoUse::test_one this is my fixture執(zhí)行test_onePASSEDtest_a.py::TestAutoUse::test_two this is my fixture執(zhí)行test_twoPASSEDtest_a.py::TestAutoUse::test_three this is my fixture執(zhí)行test_threePASSED...
從上面的運(yùn)行結(jié)果可以看出逆屡,在方法 myfixture() 上面添加了裝飾器 @pytest.fixture(autouse="true")圾旨,測試用例無須傳入這個(gè) fixture 的名字,它會自動在每條用例之前執(zhí)行這個(gè) fixture康二。
fixture 傳遞參數(shù)
測試過程中需要大量的測試數(shù)據(jù)碳胳,如果每條測試數(shù)據(jù)都編寫一條測試用例,用例數(shù)量將是非常寵大的沫勿。一般我們在測試過程中會將測試用到的數(shù)據(jù)以參數(shù)的形式傳入到測試用例中挨约,并為每條測試數(shù)據(jù)生成一個(gè)測試結(jié)果數(shù)據(jù)。
這時(shí)候可以使用 fixture 的參數(shù)化功能产雹,在 fixture 方法加上裝飾器 @pytest.fixture(params=[1,2,3])诫惭,就會傳入三個(gè)數(shù)據(jù) 1、2蔓挖、3夕土,分別將這三個(gè)數(shù)據(jù)傳入到用例當(dāng)中。這里可以傳入的數(shù)據(jù)是個(gè)列表瘟判。傳入的數(shù)據(jù)需要使用一個(gè)固定的參數(shù)名 request 來接收怨绣。
創(chuàng)建文件名為“test_params.py”,代碼如下:
import pytest@pytest.fixture(params=[1, 2, 3])def data(request): return request.paramdef test_not_2(data): print(f"測試數(shù)據(jù):{data}") assert data < 5
運(yùn)行結(jié)果如下:
...test_params.py::test_not_2[1]PASSED [ 33%]測試數(shù)據(jù):1test_params.py::test_not_2[2] PASSED [ 66%]測試數(shù)據(jù):2test_params.py::test_not_2[3] PASSED [100%]測試數(shù)據(jù):3...
從運(yùn)行結(jié)果可以看出拷获,對于 params 里面的每個(gè)值篮撑,fixture 都會去調(diào)用執(zhí)行一次,使用 request.param 來接受用例參數(shù)化的數(shù)據(jù)匆瓜,并且為每一個(gè)測試數(shù)據(jù)生成一個(gè)測試結(jié)果赢笨。在測試工作中使用這種參數(shù)化的方式,會減少大量的代碼量驮吱,并且便于閱讀與維護(hù)茧妒。
多線程并行與分布式執(zhí)行
假如項(xiàng)目中有測試用例 1000 條,一條測試用例需要執(zhí)行 1 分鐘左冬,一個(gè)測試人員需要 1000 分鐘才能完成一輪回歸測試桐筏。通常我們會用人力成本換取時(shí)間成本,加幾個(gè)人一起執(zhí)行拇砰,時(shí)間就會縮短梅忌。如果 10 人一起執(zhí)行只需要 100 分鐘绊袋,這就是一種并行測試,分布式的場景铸鹰。
pytest-xdist 是 pytest 分布式執(zhí)行插件,可以多個(gè) CPU 或主機(jī)執(zhí)行皂岔,這款插件允許用戶將測試并發(fā)執(zhí)行(進(jìn)程級并發(fā)),插件是動態(tài)決定測試用例執(zhí)行順序的蹋笼,為了保證各個(gè)測試能在各個(gè)獨(dú)立線程里正確的執(zhí)行,應(yīng)該保證測試用例的獨(dú)立性(這也符合測試用例設(shè)計(jì)的最佳實(shí)踐)躁垛。
安裝
pip install pytest-xdist
多個(gè) CPU 并行執(zhí)行用例剖毯,需要在 pytest 后面添加 -n 參數(shù),如果參數(shù)為 auto教馆,會自動檢測系統(tǒng)的 CPU 數(shù)目逊谋。如果參數(shù)為數(shù)字,則指定運(yùn)行測試的處理器進(jìn)程數(shù)土铺。
pytest -n auto pytest -n [num]
案例
某個(gè)項(xiàng)目有 200 條測試用例胶滋,每條測試用例之間沒有關(guān)聯(lián)關(guān)系,互不影響悲敷。這 200 條測試用例需要在 1 小時(shí)之內(nèi)測試完成究恤,可以加個(gè)-n參數(shù),使用多 CPU 并行測試后德。運(yùn)行方法:
pytest -n 4
進(jìn)入到項(xiàng)目目錄下部宿,執(zhí)行 pytest 可以將項(xiàng)目目錄下所有測試用例識別出來并且運(yùn)行,加上 -n 參數(shù)瓢湃,可以指定 4 個(gè) CPU 并發(fā)執(zhí)行理张。大量的測試用例并發(fā)執(zhí)行提速非常明顯。
結(jié)合 pytest-html 生成測試報(bào)告
測試報(bào)告通常在項(xiàng)目中尤為重要绵患,報(bào)告可以體現(xiàn)測試人員的工作量雾叭,開發(fā)人員可以從測試報(bào)告中了解缺陷的情況,因此測試報(bào)告在測試過程中的地位至關(guān)重要藏雏,測試報(bào)告為糾正軟件存在的質(zhì)量問題提供依據(jù)拷况,為軟件驗(yàn)收和交付打下基礎(chǔ)。測試報(bào)告根據(jù)內(nèi)容的側(cè)重點(diǎn)掘殴,可以分為 “版本測試報(bào)告” 和 “總結(jié)測試報(bào)告”赚瘦。執(zhí)行完 pytest 測試用例,可以使用 pytest-HTML 插件生成 HTML 格式的測試報(bào)告奏寨。
安裝
pip install pytest-html
執(zhí)行方法
pytest --html=path/to/html/report.html
結(jié)合 pytest-xdist 使用
pytest -v -s -n 3 --html=report.html --self-contained-html
生成測試報(bào)告
如下圖:
生成的測試報(bào)告最終是 HTML 格式起意,報(bào)告內(nèi)容包括標(biāo)題、運(yùn)行時(shí)間病瞳、環(huán)境揽咕、匯總結(jié)果以及用例的通過個(gè)數(shù)悲酷、跳過個(gè)數(shù)、失敗個(gè)數(shù)亲善、錯(cuò)誤個(gè)數(shù)设易,期望失敗個(gè)數(shù)、不期望通過個(gè)數(shù)蛹头、重新運(yùn)行個(gè)數(shù)顿肺、以及錯(cuò)誤的詳細(xì)展示信息。報(bào)告會生成在運(yùn)行腳本的同一路徑渣蜗,需要指定路徑添加--html=path/to/html/report.html 這個(gè)參數(shù)配置報(bào)告的路徑屠尊。如果不添加 --self-contained-html 這個(gè)參數(shù),生成報(bào)告的 CSS 文件是獨(dú)立的耕拷,分享的時(shí)候容易千萬數(shù)據(jù)丟失讼昆。
pytest 框架 assert 斷言使用(附)
編寫代碼時(shí),我們經(jīng)常會做出一些假設(shè)骚烧,斷言就是用于在代碼中捕捉這些假設(shè)浸赫。斷言表示為一些布爾表達(dá)式,測試人員通常會加一些斷言來斷定中間過程的正確性赃绊。斷言支持顯示最常見的子表達(dá)式的值掺炭,包括調(diào)用,屬性凭戴,比較以及二元和一元運(yùn)算符涧狮。Python使用 assert(斷言)用于判斷一個(gè)表達(dá)式,在表達(dá)式條件為 false 的時(shí)候觸發(fā)異常么夫。
使用方法:
assert True #斷言為真assertnot False #斷言為假
案例如下:
assert "h" in "hello" #判斷h在hello中assert 5>6 #判斷5>6為真 assert not True #判斷xx不為真assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'} #判斷兩個(gè)字典相等
如果沒有斷言者冤,沒有辦法判定用例中每一個(gè)測試步驟結(jié)果的正確性。在項(xiàng)目中適當(dāng)?shù)氖褂脭嘌缘祷荆瑏韺Υa的結(jié)構(gòu)涉枫、屬性、功能腐螟、安全性等場景檢查與驗(yàn)證愿汰。
點(diǎn)擊領(lǐng)取:自動化+側(cè)開+性能+簡歷+面試核心教程資料
http://qrcode.testing-studio.com/f?from=jianshu&url=https://ceshiren.com/t/topic/3595