0:框架簡介
pytest,rf(學關鍵字語法,報告漂亮),unitest
pytest是python的第三方單元測試框架,可以做系統測試,比unitest更簡潔和高效,執行315種以上的插件,
同時兼容unittest框架,在unittest框架遷移到pytest框架的代碼不需要重寫代碼
unittest框架遷移到pytest框架的時候不需要重寫代碼
純python代碼的自動化測試框架
1:Pytest框架簡介:
接口測試方案:python
一:工具類:純手工測試,用工具來做(postman jemeter soapui)--入門簡單,不好擴展(后面很多框架定制化)
二:代碼類:現成的python框架:unitest(單元測試比較多,最原始的解釋器自帶的,不需要安裝,不支持定制化,分布式) pytest(高級,效率高,支持定制化) nose
rf(報告篇評論,需要學會--封裝關鍵字)
pytest和nose都是unitest擴展的更高級的一個庫,框架,基於unitest
三:測試平台:現成平台,公司自己定制開發的,不對外 (融合jmeter,) 綜合平台
前端
后端
執行機制----框架pytest(一般融合了禪道,框架,郵件各種功能)
pytest是python的第三方單元測試框架,可以做系統測試,比unitest更簡潔和高效,支持315種以上的插件,
同時兼容unittest框架,在unittest框架遷移到pytest框架的代碼不需要重寫代碼
unittest框架遷移到pytest框架的時候不需要重寫代碼
純python代碼的自動化測試框架
pytest對比unitest框架的優勢:高級,效率高,支持定制化,支持分布式,支持315種以上的豐富插件,還能向下兼容unitest
pytest靈活:
1:定制化(定制化用例執行,定制化報告)
2:環境清除也靈活 以及各方面做的都比unittest更加靈活
pytest更加靈活,便捷,效率更高, 還支持分布式(分布式是其他框架做不了)
分布式:1000個接口用例怎么跑,一條條跑很費勁,時間長,找幾個同時分擔執行測試用例(pytets獨有的性質)
2:pytest框架環境搭建:
pip pytest 安裝pytest
pip install pytest-html 安裝原生態報告模板--自帶的(有點垃圾)
Required-by: pytest-xdist(分布式測試), pytest-metadata, pytest-html, pytest-forked, allure-pytest
100個接口用例,正常是一個個用例跑,時間很長,
分布式-多個業務用例多條線來跑,提高效率(分布式設計用例---分布式邏輯設計,不要出現耦合,關聯性太強的東西,否則會等待的)
3:pytets執行測試用例
設計測試用例時候注意點(必須遵循的規則,否者不識別):
1:.py測試文件必須以test(test_xxx)開頭(或者以_test結尾)
2:測試類必須以Test開頭,並且不能有init方法-----測試類Test開頭
3:測試方法必須以test_開頭
4:斷言必須使用assert
4:一般做項目是新建package包的
項目文件
lib庫文件 (登錄接口源代碼,其他接口公共的類,封裝的庫,登錄的,訂單的)(包)
data文件 (參數化數據,excel文件,yaml文件,csv文件---測試文件,用例,文檔)(可以是普通文件夾)
test_case文件 (放測試用例的 )(包)
test_func01.py(測試用例,寫的最好見名知意)
report文件 (存放測試報告的普通文件夾)
config (配置文件)
5:pytest函數級別
函數級別的測試用例必須test_開頭:如下test_tc01,test_tc02兩個測試用例
import pytest def test_tc01(): #定義函數類型測試用例 assert 1+1==2 #斷言 def test_tc02(): assert 1+1==3 #斷言 if __name__ == '__main__': pytest.main(["test_func01.py"]) #我主動運行我的pytest框架(自動調用所有的test測試函數,按照順序依次運行,test開頭的用例自動識別)
6:pytest類級別(工作一般以類位單元,一個模塊一個類,登錄類,訂單類,購物類)
類級別的測試l類必須以Test開頭,並且類李不能有init方法,類里面的函數都是test_開頭
封裝好函數和類就行,其他的交給框架,設置好,框架幫你自動組織怎么運行
封裝為了分層,后面更好維護,代碼結構整潔
import pytest class Test_login(): #登錄模塊的測試類 def test_login01(self): print("---test_login01----") assert 1 + 1 == 2 def test_login02(self): print("---test_login02----") assert 1 + 1 == 3 if __name__ == '__main__': pytest.main(["test_func01.py","-s"]) #框架自己調用函數 需要打印對應的信息,需要在列表里面加-s
7:自動化測試里面的環境初始化與清除
環境初始化目的:
清空測試環境的垃圾數據,前置條件
需不需要分層:需要
比如:課程模塊:課程模塊的初始化需要
1:刪除所有的課程
2:新增我們的一些課程(這個給修改/查詢/刪除接口使用) 模塊級別的(大的課程模塊第一件事就是刪除以前的課程)
干掉數據后假如需要刪除課程,這個接口需要單獨的fixture的初始化,增加課程才能刪除,其他的接口不需要這個fixture初始化,)
分層:模塊層次的初始化,某個接口也需要初始化----框架的分層
條件初始化要和接口掛鈎,接口該怎么就要怎么設計
環境初始化和清除,
一頭一尾,兩個不同概念,(環境的初始化也可以是清除數據)
一個接口可以多個級別的fixture,可以
分布式:1:並行執行 2:分布式
優化運行時間:分布式,(搭建環境麻煩)
什么是環境初始化:
做這個用例之前想要做個操作,初始化動作,比如登錄,首先需要連上這個項目(要先能ping通),
環境初始化--比如課程新增需要數據全部清空,也是環境初始化
功能測試:保證測試環境數據和跑什么系統的,或者后台有什么進程執行,或者項目里面測試這功能,功能里面有沒有垃圾數據要清除 做個初始化
unittest:最基礎的框架,python自帶(環境初始化和數據清除用setup和teardown)
jemeter:也有環境清除和初始化
不管做什么測試比如(功能,自動化,性能)都要對當前測試環境初始化,做完后要垃圾數據進行回收(特別是自動化,不然很多用例明明是對的會失敗)
每次做一個場景,模塊的時候,看看模塊有沒有需要前置的或者環境清除的步驟(基本操作流程)
pytest是unittest的升級版,對環境清除操作非常靈活(分層分級)
pytest:fixture操作類進行環境初始化 @fixture這樣的一個裝飾器
pytest的fixture操作
環境初始化與清除
pytest提供的fixture實現unitest中的setup/teardown功能,可以在每次執行case之前初始化數據
不同的是,fixture可以只在執行某幾個特定case前運行,只需要在運行case前調用即可,比setup/teardown使用靈活
pytest的初始化和清除可以類里面寫個setup_class方法做,以類為單元,模塊,包,方法為單元都可以,也可以用fixture來做
7:pytest前置和后置條件(環境初始化與清除):
環境初始化
1:清除以前的數據
2:測試的時候不是每個接口都要執行,可以定制化執行,固定執行某些接口,先執行刪除用例,
但是數據已經被清除了,無法刪除,修改--需要新增一批測試數據,所以這時候需要環境初始化和清除的想法
setup_class:類里面類級別的初始化,teardown
pytest初始化和前置條件,很多接口用例本身需要初始化,初始化分為很多層,
可以在整個外面做,也可以在里面做,測試類的初始化可以在類里面定義
import pytest class Test_login(): #登錄模塊的測試類 #該測試類---有個前置的操作(初始化) def setup_class(self): #類級別的初始化--可選項 #一個項目,先登錄,再購物,登錄就是購物類的前置條件,可以放在setup_class里面 print("執行測試類之前,我需要執行操作") def test_login01(self): print("---test_login01----") assert 1 + 1 == 2 def test_login02(self): print("---test_login02----") assert 1 + 1 == 3 def teardown(self): #看業務本身需不需要初始化和清除環境,--可選項 print("------該測試類的環境清除-----") if __name__ == '__main__': pytest.main(["test_func01.py","-s"])
8:pyets種有四種級別的setup和teardown:
1:setup_module和teardown_module,在整個測試用例所在的文件中所在的文件中所有的方法運行前和運行后運行,只運行一次---模塊的
2:setup_class和teardown_class,在整個測試文件中的一個class中所有的用例的簽后運行 ----class類
3:setup_method和teardown_method,在class內的每個方法運行前后運行 ---------方法的
4:setup_function和teardown_function,在非class下屬的每個測試方法的前后運行 ----函數的
分層分級(不同級別有不同方法)
8:pytest里面的數據初始化裝飾器fixture參數說明
fixture_function: Optional[_FixtureFunction] = None, *, scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function", --scope參數:級別 params: Optional[Iterable[object]] = None,------------------------------- params:參數 autouse: bool = False,---------------------------------------------------- autouse:是否自動化執行 ids: Optional[ Union[ Iterable[Union[None, str, float, int, bool]], Callable[[Any], Optional[object]], ] ] = None, name: Optional[str] = None
@pytest.fixture(scope=xxx,params=xxx,autouse=xxx)
fiixture裝飾器可以傳單三個參數
1:scope參數:初始化清除定義級別
2:params:參數
3:autouse:是否自動化執行
一:fixture 函數級別的初始化,環境初始化
import pytest #函數級別的@pytest.fixture()初始化操作 @pytest.fixture() #標記函數是個初始化操作,標記后需要傳給每個函數statr_func這個函數名才會執行初始化操作(函數級別的) def statr1_func(): #這不是測試函數,一個普通函數,pytest執行用例只能識別test開頭的方法和函數,所以pytest.main不會執行(不參加pytest用例) print("------初始化操作1------") @pytest.fixture() def statr2_func(): print("------初始化操作2------") #fixture:有哪些操作(可以多個初始化可以一起調,需要兩個初始化,需要連接,需要登錄) #這種寫法很方便,函數需要statr_func1函數做一個初始化操作可以調用statr_func1這個函數,---def test_001(statr1_func): # 需要其他初始化方法可以選擇性調用其他初始化函數,傳遞函數名就行(靈活選擇)----def test_002(statr2_func): #函數初始化操作需要傳遞幾個函數也可以多個函數名傳遞--def test_003(statr2_func,statr1_func): #方便靈活 def test_001(statr1_func): print("-----test01------") def test_002(statr2_func): print("-----test02 ------") def test_003(statr2_func,statr1_func): print("-----test03 ------") if __name__ == '__main__': pytest.main(["test_pytest.py","-s"])
二:類級別的初始化class,可以使用setup做初始化,也可以使用fixture做初始化
import pytest @pytest.fixture(scope="class") #類級別的初始化函數 scope="class" 就是把這個初始化定義成類級別的 def statr1_func(): print("------初始化操作1------") class Test_00: #需要執行 Test_00測試類,需要做初始化(可以setup_class) # def setup_class(self): # print("類內部的初始化,") #只對類有用,類級別的,類里只做一次(幾個類的初始化操作一樣這種不適合,需要重復寫) #fixture初始化類就是避免重復代碼 def test_001(self,statr1_func): print("-----test01------") def test_002(self,statr1_func): print("-----test02 ------") if __name__ == '__main__': pytest.main(["test_pytest01.py","-s"])
類級別初始化fixture,雖然test_001和test_002都調用了statr1_func這個類級別的初始化函數,但是執行類測試用例的時候只執行statr1_func初始函數一次
多個類都可以調用statr1_func這個類級別的初始化方法,調用的時候最好放在類里的第一個函數,后面的函數可以不傳(因為對應的是類級別的初始化)
import pytest @pytest.fixture(scope="class") #類級別的初始化函數 def statr1_func(): print("------初始化操作1------") #一個模塊里面有函數用例也有類的用例怎么做:(class級別的初始化只對類有用,對函數沒用) def test_003(statr1_func): #測試函數, print("-----test03------") class Test_00: #需要執行test00測試類,需要做初始化(可以setup_class) def test_001(self,statr1_func): print("-----test01------") def test_002(self,statr1_func): print("-----test02 ------") if __name__ == '__main__': pytest.main(["test_pytest01.py","-s"])
初始化方法statr1_func定義成class類級別的,函數級別的測試測試用例test__003調用初始化函數會執行一次,
class類級別的測試用例Test_00調用初始化函數會執行一次(一共執行兩次)
看級別的,整個模塊的級別的化最好用module,否則有問題,fixture可以做return,會有返回值的,對應級別來做,
執行結果:
test_pytest01.py
------初始化操作1------
-----test03------
.------初始化操作1------
-----test01------
.-----test02 ------
三:類級別初始化實際代碼:初始化操作是登錄操作
#課程模塊的測試類 import pytest from lib.api_lib.lesson import Lesson from lib.api_lib.lesson import Login from tools.execlMethod import get_excelData import json import os @pytest.fixture(scope="class") #類級別的初始化函數 def start_func(): global sessionid sessionid = Login().login('{"username":"auto","password":"sdfsdfsdf"}') class Test_lesson: #1:課程新增接口,前置條件登錄(封裝完一個方法后想辦法做數據驅動),課程增加需要通過excel表用例來做 @pytest.mark.parametrize("inData,repsData", get_excelData('2-課程模塊', 2, 26)) def test_lesson_add(self,start_func,inData,repsData): reps=Lesson(sessionid).lesson_add(inData) print(reps) assert reps["retcode"]==json.loads(repsData)["retcode"] if __name__ == '__main__': pytest.main(["test_lesson01.py", "-s", "--alluredir", "../report/tmp"]) os.system("allure serve ../report/tmp")
四:模塊級別的初始化mudule,不管是類還是方法 @pytest.fixture(scope="module")
模塊(module)級別的初始化,(整個模塊所有的類所有的東西要做一步操作,可以使用module這個模式)
只在模塊運行前面只做一次,后面不做了,哪怕多調用也沒用,一個模塊里面有test_003函數測試用例,
也有classTest_00類級別的測試用例,定義一個模塊級別的初始化函數statr1_func
函數里面調用初始化方法def test_003(statr1_func):和類里面的方法調用初始化方法test_001(self,statr1_func):,test_001(self,statr1_func):
整個模塊執行的時候初始化函數都只執行一次(不管你這個模塊里面調用多少次)
import pytest @pytest.fixture(scope="module") #模塊級別的初始化函數 def statr1_func(): print("------初始化操作1------") #一個模塊里面有函數用例也有類的用例怎么做:(class級別的初始化只對類有用,對函數沒用) def test_003(statr1_func): #測試函數, print("-----test03------") class Test_00: #需要執行test00測試類,需要做初始化(可以setup_class) # def setup_class(self): # print("類內部的初始化,") #只對類有用,類級別的,類里只做一次(幾個類的初始化操作一樣這種不適合,需要重復寫) # #fixture初始化類就是避免重復代碼
def test_001(self,statr1_func): print("-----test01------") def test_001(self,statr1_func): print("-----test02 ------") if __name__ == '__main__': pytest.main(["test_pytest01.py","-s"])
執行結果:test_pytest01.py
------初始化操作1------
-----test03------
.-----test01------
.-----test02 ------
五:session的級別的初始化,跨模塊的,package級別的
在這個模塊下面所有的都會調用(包級別的,包里面運行前做個環境清除)
需要在testcase文件夾里面創建一個conftest.py模塊,這個
固定名稱,pytest自動識別這個名稱
testcase里面:新增課程前面需要登錄,增加課程前面需要清除數據,需要2個級別的初始化,1:登錄 2:整個環境的清除
test_case(測試用例文件夾)創建一個:conftest.py文件 里面寫包級別的初始化
conftest.py文件里也能寫類級別和模塊級別的初始化,而且不需要調用,這個conftest.py模塊是pytest自動識別導入的
test_case #文件夾 conftest.py import pytest #包級別的初始化,在運行整個包之前做個初始化,包里面不同作用域,每個包里面都可以放一個,每個包里面的操作都可以不一樣 @pytest.fixture(scope="session",autouse=True) #session級別的處於時候autouse=True默認自動執行 def start_demo(request): #包的開始 print("我是整個包的初始化") def fin(): #尾部這是包級別的,整個包做完后做個環境數據的清除 包的結束 print('---測試完成,包的數據清除---') request.addfinalizer(fin) #回調,當我整個包運行完了后回調fin這個方法 #fixture的參數autouse: bool = False,---自動執行參數
#session的級別,包里面有很多模塊,很多模塊需要對整個包進行初始化在conftest.py里面做模塊的數據初始化和清除(conftest.py只對當前包有用)
六:兩種調用初始化和清除函數的方式+初始化清除函數的返回值的使用
import pytest @pytest.fixture() def befor_func(): print('xxxxxxxxxxxxx測試用例的初始化xxxxxxxxxxxxxxxx') yield 10 print('zzzzzzzzzzzzzzzzzz測試用例的清除zzzzzzzzzzzzzz') def test_001(befor_func): #調用初始化和清除方式一:直接在測試用例里傳遞初始化清除函數的函數名來調用 print("測試用例001") res=befor_func #如果初始化清除函數有返回值,可以直接這樣接收參數來使用 print(res) @pytest.mark.usefixtures('befor_func') #調用初始化和清除方式二:使用usefixtures放在測試用例前面直接調用初始化清除函數 def test_002(): print("測試用例002") if __name__ == '__main__': pytest.main(["test1.py",'-s'])
七:pytest前置條件+后置條件的兩種寫法
1:使用yield關鍵字來是實現 推薦使用這種,因為yield關鍵字能返回函數的值
import pytest @pytest.fixture() def befor_func(): print('xxxxxxxxxxxxx測試用例的初始化xxxxxxxxxxxxxxxx') yield 10 #yield后面跟的是測試用例的后置條件,支持用例執行后就執行yield里的內容 print('zzzzzzzzzzzzzzzzzz測試用例的清除zzzzzzzzzzzzzz') def test_001(befor_func): print("測試用例001") res=befor_func print(res) if __name__ == '__main__': pytest.main(["test1.py",'-s'])
2:使用finc()函數來實現 這種就不能返回返回值了
import pytest @pytest.fixture() def befor_func(request): print('xxxxxxxxxxxxx測試用例的初始化xxxxxxxxxxxxxxxx') def fin(): #尾部這是后置條件,測試用例執行后就會調用這個函數
print('zzzzzzzzzzzz測試用例的清除zzzzzzzzzzz') request.addfinalizer(fin) #回調,當我整個包運行完了后回調fin這個方法
def test_001(befor_func): print("測試用例001") if __name__ == '__main__': pytest.main(["test1.py",'-s'])
9:pytest數據驅動(參數化)
pytest數據驅動的意義:
參數化(登錄用例4條,每一個賬號密碼都不同,使用框架把4個用例全部執行完,不需要for循環遍歷執行,采用數據驅動方案來做)
pytest內置裝飾器@pytest.mark.parametrize可以讓測試數據參數化,把測試數據單獨管理,類似ddt數據驅動的作用,方便代碼和測試數據分離
@pytest.mark.parametrize("a",[1,2,3]): 參數化傳一組參數
@pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)]) 參數化傳多組參數
登錄賬戶密碼(name和psw不同的用例組合,一個接口幾十個用例怎么做----幾十組數據----傳的參數不同(什么請求方式和各種都一樣)
可以把name和psw分別采取多組數據進行參數化,數據分離,一個接口跑4次,每次用不同的參數)
import pytest #[(1,2),(3,4),(5,6)] [1,2,3] class Test_login(): def setup_class(self): print("執行測試類之前,我需要執行操作") @pytest.mark.parametrize("a",[1,2,3]) #("變量名",[1,2,3]),數據需要封裝成一個列表,多個數據需要封裝成列表嵌套元組 ----數據驅動 def test_login01(self,a): #數據驅動,一定要把變量名a引入引來,不然無法參數化 print("---test_login01----") assert 1 + 1 == a @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)]) #數據驅動傳多組參數 def test_login02(self,a,b): print("---test_login02----") assert a + 1 == b def teardown_class(self): print("------該測試類的環境清除-----") if __name__ == '__main__': pytest.main(["test_func01.py","-s"])
10:pytest結合allure報告操作
一:pytest自帶的報告框架 pytest-html
二:allure環境搭建(allure是報告庫不是python專屬的,很全面的框架)-allure報告漂亮
1:下載allure.zip(壓縮包)
2:解壓allure.zip到一個文件目錄
3:將allure-2.13.3\bin路徑添加到環境變量path
4:pip install allure-pytest -------allure報告本身不是很漂亮,通過allure-pytest這個庫可以定制化報告,讓報告變得很漂亮
5:驗證(cmd輸入allure)
三:allure和pytest聯合執行生成報告:運行兩條語句
1:執行pytest單元測試,生成的allure報告需要的數據存在/tmp目錄
pytest -sq --alluredir=../report/tmp #pytest把allure報告的生成的中間文件放到一個臨時文件里面(pytets生成報告,需要數據,所以先把數據存起來)
#所有的報告需要數據支持的,數據來源pytest框架本身,結果數據存到一個文件,存在../report/tmp文件夾
#tmp臨時文件,一般json格式
2:執行命令,生成測試報告
allure generate ../report/tmp -o ../report/report -clean #allure指令生成對應報告
四:allure模擬代碼
import pytest import os class Test_login(): def setup_class(self): print("執行測試類之前,我需要執行操作") @pytest.mark.parametrize("a",[1,2,3]) def test_login01(self,a): print("---test_login01----") assert 1 + 1 == a @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)]) def test_login02(self,a,b): print("---test_login02----") assert a + 1 == b def teardown_class(self): print("------該測試類的環境清除-----") if __name__ == '__main__': #需要打印對應的信息,需要在列表里面加-s #1:--alluredir ---生成臨時文件,測試用例的結果數據放到目錄 --alluredir 存放目錄 pytest.main(["test_func01.py","-s","--alluredir","../report/tmp"]) #框架自己調用函數 #通過--alluredir把allure需要的數據存到../report/tmp這個路徑下面 #../--所在路徑的父級別目錄是test_case的目錄隔壁鄰居report文件下tmp,專門放alluer報告生成的需要的數據源 # 2:臨時數據沒有報告的,allure generate allure才會生成報告 -----allure生成器生成allure報告--generate allure生成器,cmd指令 #需要os模塊os.system()調用指令可以在local的cmd里面敲 os.system("allure generate ../report/tmp -o ../report/report --clean") #os.system("allure generate 報告需要的數據 -o 報告存放目錄 --clean") #-o生成 #allure generate生成報告指令,把../report/tmp 的文件-o生成報告out out一下,生成的報告放在../report/report #--clean把上次報告清除一下用--clean
#allure報告生成的是一個服務,(本地服務)和jinkins結合,放在整個里面去集成,放到公共服務器里面
五:allure報告的優化
import pytest import os import allure @allure.feature("登錄模塊") #一級標題,大模塊標題(類標簽) class Test_login(): def setup_class(self): print("執行測試類之前,我需要執行操作") @allure.story("登錄login01") # 二級標簽(每個接口的標簽) @allure.title("login01") # 標題,每個用例帶個標題(報告體現在每個測試用例)(一個接口有幾個用例,title用例的標簽) @pytest.mark.parametrize("a",[1,2,3]) def test_login01(self,a): print("---test_login01----") assert 1 + 1 == a @allure.story("登錄login02") # 二級標簽,定制allure報告層級 @allure.title("login02") #標題,每個用例帶個標題(報告體現在每個測試用例) @pytest.mark.parametrize("a,b", [(1,2),(3,4),(5,6)]) def test_login02(self,a,b): print("---test_login02----") assert a + 1 == b def teardown_class(self): print("------該測試類的環境清除-----") @allure.feature("購物模塊") class Test_Shopping(): @allure.story("shopping") @allure.title("shopping01") @pytest.mark.parametrize("a,b", [(1, 2), (3, 4), (5, 6)]) def test_shopping(self, a, b): print("---test_login02----") assert a + 1 == b if __name__ == '__main__': pytest.main(["test_func01.py","-s","--alluredir","../report/tmp"]) os.system("allure generate ../report/tmp -o ../report/report --clean") #allure報告生成的是一個服務,(本地服務)和jinkins結合,放在整個里面去集成,放到公共服務器里面
六:其他知識點:
測試用例一般寫在excel表格文件里面,數據分離(維護好excel就行)
pytest--從頭到尾到報告執行發郵件
字典是一種存儲類型,json是一種格式(完全不同)
11:pytest參數解析
pytest.main(['test_boss.py','-s','-k test_modify_psw','--alluredir=tmp/my_allure_results'])
test_boss.py 指定測試用例文件,
-s 顯示print語句
-k test_modify_psw 指定某個測試用例
-n 表示用兩個進程啟動測試腳本
生成報告緩存文件 --alluredir=tmp/my_allure_results
os.system('allure serve tmp/my_allure_results') 打開測試報告,命令行需要python 的os模塊調用
12:pytest的初始化和清除:
import pytest #假設啟動被測app的時候需要去填寫配置項信息,每個的端口號不同,多終端需要兩個appim server #這時候setup_module和teardown_module不能傳參,搞不定,需要換一種方法做測試用例的初始化和清除, #setup_module以模塊為作用域,不寫module以測試用例(測試函數)為作用域 # def setup_module(): #測試用例之前執行,原始的setup和teardown有個缺陷,里面不能傳參數, # #默認test級別,每個測試用例執行的時候都會執行一次,希望當前某個模塊執行的時候只執行一次(不管里面用例執行多少次) # #setup初始化和tear_down升個級,升級成module模塊級別的 # print("啟動被測app") # print('連接appium服務') # # def teardown_module(): # print('關閉被測app') # print('斷開appium服務') #定義個函數,名字隨便取 使用@pytest.fixture裝飾器把這個函數裝飾成初始化清除函數 @pytest.fixture(scope='module') #作用域默認test,初始化,加裝飾器,初始化清除函數,autouse=True(自動執行)這種方法不建議使用 # def before_test(): #初始化函數升級作用域到module模塊級別 print("啟動被測app") print('連接appium服務') yield #后面寫清除動作, after_test() #清除函數,清除函數並不會直接被初始化函數使用,我們必須放在初始化函數yiled后面才能回被調用 def after_test(): print('關閉被測app') print('斷開appium服務') #目前一共有兩個port,需要測試兩個手機,兩個多終端,before_test需要裝飾器標記 #測試用例的參數化 @pytest.mark.usefixtures('before_test') #這表示調用某個自定義的初始化函數,括號里面的字符串寫被調用函數的名字 @pytest.mark.parametrize('psw',['boss123','boss456']) def test_app(psw): #測試用例,可能涉及到其他參數,比如需要一些配置信息,測試用例涉及到參數,
#多組參數需要使用裝飾器pytest.mark.parametrize(數據驅動),psw傳參和形參名字對應的 print('測試boss app') print(f'登錄測試賬號{psw}') if __name__ == '__main__': pytest.main(['pytest_ywt.py','-s'])
13:pytest之:不只是測試函數test_app能參數化,初始化函數before_test也能參數化
重點:測試用例的參數化+初始化清除函數的參數化 初始化清除函數的參數化能夠實現appium的多終端測試
初始化清除函數的參數化,方法很多種:
before_test初始化函數注入參數,因為print(f'連接appium服務{port}')里面port需要變化的,
@pytest.fixture(scope='module',params=[(4723,),(4727,)]) :初始化清除函數的參數化
始化函數裝飾器里面加params參數傳參,port=request.param[0] 來調用params里的參數
#初始化清除函數的參數化:只傳單個參數
import pytest @pytest.fixture(scope='module',params=[(4723,),(4727,)]) #初始化清除函數的參數化params def before_test(request): port=request.param[0] #param[0],假如注入多個參數一個port和一個data--需要params傳元組, #params=[(4723,100),(4727,200)],一個參數的話不需要寫成列表嵌套元素,
#params[0]代表獲取元組第一個 print("啟動被測app") print(f'連接appium服務{port}') yield #后面寫清除動作, after_test() #request是pytest的對象,我們在用對象里面的方法的時候pycham不會自動幫我們取顯示名字,
#它也不知道request里面到底什么內容 def after_test(): print('關閉被測app') print('斷開appium服務') @pytest.mark.usefixtures('before_test') @pytest.mark.parametrize('psw',['boss123','boss456']) def test_app(psw): print('測試boss app') print(f'登錄測試賬號{psw}') if __name__ == '__main__': pytest.main(['pytest_ywt.py','-s'])
#初始化清除函數的參數化:傳多個參數
import pytest
@pytest.fixture(scope='module',params=[(4723,'xiaomi'),(4727,'meizu')]) def before_testquest): port=request.param[0] #param[0],假如注入多個參數一個port和一個data,需要params傳元組,params=[(4723,100),(4727,200)], #一個參數的話不需要寫成列表嵌套元素,request.params[0]代表獲取元組第一個 device=request.param[1] #request.param[1]對應元素里面第二個參數, print(f"在{device}啟動被測app") print(f'連接appium服務{port}') yield #后面寫清除動作, after_test() #request是pytest的對象,(固定寫法:request.param)
#我們在用對象里面的方法的時候pycham不會自動幫我們去顯示名字,它也不知道request里面到底什么內容 def after_test(): print('關閉被測app') print('斷開appium服務') @pytest.mark.usefixtures('before_test') @pytest.mark.parametrize('psw',['boss123','boss456']) def test_app(psw): print('測試boss app') print(f'登錄測試賬號{psw}') if __name__ == '__main__': pytest.main(['pytest_ywt.py','-s'])
14:pytest框架執行代碼也能在cmd里面直接輸入命令執行
xxx\test_case> pytest -s 在test_case這個目錄執行會運行test_case文件里面所有的測試文件(test開頭的測試用例)
15:分布式
設置用例每個模塊獨立,有什么前置做到模塊里面,比如測試10個模塊,用相關聯來做,不能做分布式(並發執行)
每個模塊獨立還能定制執行那個模塊,關聯性太強做不到
最好做到每個接口都獨立化(前置條件做好)不要做太大關聯性的接口
每一層都能做環境清除和定制化(包,模塊。類,函數)分層,為后面mark(定點執行哪些用例)和分布式打基礎
分布式:必須做到用例的隔離(低耦合,高內聚),用例走串行風險很大,很難維護
3000個請求,全部獨立化,然后分布式來做(效率提高幾倍--幾十倍)
16:分布式的實現
分布式的核心點:封裝設計:相互獨立,登錄和課程相互獨立,至少模塊為單元要相互獨立,封裝相互獨立,接口用例之間最好也相互獨立 才能進行分布式
一:pytest分布式環境搭建和理論介紹:
第一步:安裝一個庫 pip install pytest-xdist 分布式運行插件,可以做分布式(這個庫有兩種運行方式)
運行方式:
1:串行,順序運行,串行運行,從頭到尾
2:並行:pytest-xdist做分布式有兩種,一種多核,一種多台機器
一:多核:單機多核來做(同時跑) 使用-n參數
電腦多核有假有真:超線程技術(8內核搞成16核),真8核假8核---
cpu個數:硬件,幾個cpu槽,i9900.i710--一般電腦就算一個cpu,單cpu,服務器可能有多個cpu
核數: 電腦的核數,
邏輯核數:邏輯核數可以虛擬化,8核可以變成16核(超線程技術)
多核的話xdist本身的多核的話一般用邏輯核數來做的
二:多機(可以使用虛擬機)---需要搭環境,多台機器 很麻煩,裝環境,下庫
二:測試用例比較多怎么辦:分布式 兩種情況
1:量大:多機 (需要文件報告收集還需要搭環境,做起來比較麻煩)
2:單機多核 很簡單,加-n 參數就行 (做ui和需要一些時間等待的時候時間優化特別明顯)
串行運行:本身是線程去跑的,python就一個進程,里面很多線程,
走進程的話需要多台機器來做,分量, 用例設計不好會有大問題,數據不對(用例一定要獨立化)
並行和多機:用例一定要設計好,不然數據容易出錯,邏輯獨立(不能有任何關聯,不能有前后關系) 數據和代碼封裝時候獨立化
三:分布式運行代碼
#驗證單機多核分布式 import pytest import time def test_01(): time.sleep(3) print("-----test01-----") def test_02(): time.sleep(3) print("-----test01-----") if __name__ == '__main__': # pytest.main(["test_xdist.py","-sq"]) #這是串行跑的 6s pytest.main(["test_xdist.py", "-sq","-n","8"]) #單機多核並行 加"-n","8" 參數 ,用8個核來跑,5.41s,時間少了 #或者測試用例文件目錄下 cmd,輸入 pytest test_xdist.py -n 8 也可以 這樣cmd里執行看到的結果更直觀, #多核來跑在ui里面時間提升很大,ui里面很多地方需要sleep,等待元素(有等待的的提升比較大)多核跑更快 #有等待的情況用多核跑效果越明顯 串行運行本身按照線程去跑的,python本身就一個進程,里面很多線程 走進程的話,多台機器做比較合適---分量, 用例一定要設計好,不然數據容易出錯,邏輯獨立(不能有任何關聯,不能有前后關系)----數據和代碼封裝時候獨立化 pytest cmd執行多個模塊用例:pytest test_xdist.py test_login.py -sq :運行兩個.py文件(寫多個運行多個) pytest cmd執行多個包的用例:pytest test_xdist test_login -sq :運行test_xdist包和test_login 包 還可以運行不同模塊的兩個包,加包路徑 case/test_xdist.py case2/test2_xdist.py
17:pytest的用例定制化執行 mark標簽,
所有的接口不需要全部都跑(冒煙,定制化執行某些指定的業務,) "-m","test_lesson_add"
一:pytest框架mark標簽 標記非常豐富 mark標簽
mark標簽:對於pytest,我們可以再每一個模塊,每一個類,每一個方法和用例前面都加上mark,
那樣我們在pytest運行的時候就可以只運行帶有該mark標簽的模塊,類,用例
這樣的話可以方便我們選擇執行自動化時,是執行全部用例,某個模塊用例,
某個流程用例,某個單獨用例,總之就是可以某個單獨的標簽下所有用例
mark可以標記不同層次的東西(類,函數,方法都可以標記)文件不用標記(本身就可以定制化執行)
@pytest.mark.lessson_moudle 給測試類貼個標簽,標簽名字叫lessson_moudle標識課程模塊,
各個函數,類都可以貼上標簽(類似別稱),選擇某個標簽就運行某一個(靈活方便)
什么都不選中照常運行,(全部運行,沒有限制)
mark標簽pytest運行可能報錯,
PytestUnknownMarkWarning報錯:是一個標簽的mark警告,整個pytest這么寫不識別你,但是不會報錯,只是警告,
消除警告(增加標簽欄,相當於標簽的聲明)
標簽聲明寫法:teach_sq文件夾里創建一個pytest.ini的文件(pycham需要安裝ini插件 file-setting-plugins(搜索ini)社區版似乎不行)
pycham找不到可以離線裝
teach_sq pytest.ini-----文件內容如下,相當於pytest的mark標簽聲明一下
#文件內容,markers后面把標簽全部寫上, lessson_moudle 一個類級別的mark標簽名,
#test_lesson_add,test_lesson_list,test_lesson_delete 三個函數級別的mark標簽名稱
# mark標簽名稱: 描述 這樣的格式來寫,前面聲明標簽名稱,后面是描述(隨便寫)
[pytest] markers= lessson_moudle: teach_lesson (標簽名: 描述 這樣的格式 前面lesson_modle和test_lesson_add等是mark的標簽名稱,
聲明一個標簽 :冒號后面一定要加空格,規范)
后面的描述不建議寫中文,會報錯(需要轉碼,但是這個文件python自己調用的) test_lesson_add: teach_lesson test_lesson_list: teach_lesson test_lesson_delete: teach_lesson
定制化執行test_lesson_add一個接口: "-m","test_lesson_add"
pytest.main(["test_lesson01.py","-s","-m","test_lesson_add"]) ---- -m就可以實現,mark定制化執行
定制化執行多個接口--邏輯或就行:"-m","test_lesson_add or test_lesson_delete"
pytest.main(["test_lesson01.py","-s","-m","test_lesson_add or test_lesson_delete"])
排除法排除一個-定制化除了某個接口不運行其他都運行---"-m","not test_lesson_add"
pytest.main(["test_lesson01.py","-s","-m","not test_lesson_add"])----除了test_lesson_add這個接口其他都運行
排除法排除多個-"-m","not (test_lesson_add or test_lesson_delete)"
pytest.main(["test_lesson01.py","-s","-m","not (test_lesson_add or test_lesson_delete)"])
篩選測試用例代碼:
import pytest @pytest.mark.zzzzz def test_001(): print('test_001') def test_002(): print('test_001') if __name__ == '__main__': pytest.main(["test1.py",'-s','-m','zzzzz'])
二:定制化執行:(組裝流程)
1:mark標簽:組裝流程方便
-k:匹配篩選用例名稱(可標准(全名)可模糊匹配)---指的是模塊,-k和-m類似,有or和not,
但是-k只運行所有的用例,然后有排除模式,選定模式,還要其他模式(-k在cmd里面寫的)
pytest -k test_lesson01.py :執行test_lesson01.py模塊的測試用例(sq可以不寫,只是不加打印,sq錯誤執行的時候會有很多信息可以打印,)
全部選中,測試包里面的模塊全部被選中:pytest -k test,(差不多功能的模塊寫同樣單詞命名,這樣-k可以一起匹配上)
lesson_1.py 和lesson_2.py:想運行兩個文件 pytest -k lesson (默認模糊匹配lesson)
-m: 選擇對應的標簽(標簽,mark需要打標簽才能定制化執行,其他兩個k和v不用打標簽)
-v:節點,你要運行里面的某一個某一個里面的東西:(節點指定某一個)
示例:pytest_lesson.py::Test_lesson::test_01
pytest -v pytest_lesson.py::Test_lesson::test_01
-sq:(簡化print打印信息) -s:輸出print打印 -q簡化輸出
sq,錯誤時候顯示錯誤信息,或者fail很多失敗信息,很多打印的時候寫sq打印(成功不用寫)
三:跳過/條件跳過:通過上下條件判斷這個東西要不要跑,(兩種) skip
1:跳過(沒有條件的跳過,指定跳過,不需要滿足什么條件) skip
@pytest.mark.skip("跳過test_lesson_add") 無條件跳過
接口函數前面寫@pytest.mark.skip("跳過test_lesson_add"),
這個接口就會被無條件跳過,不執行(報告里面會描述出來)
2:有條件的跳過 skipif
@pytest.mark.skipif(1==1,reason="條件需要前面完成某一個步驟--前面條件為真的時候則跳過函數,不為真執行函數") 有條件的跳過
流程化需要跳過接口,1,2,3,
1為真運行的時候才執行2,1不執行2也不執行,直接跳過)
條件從哪里來,預期和實際的做個判斷(找個前置條件做個判斷是不是為真)
如果登錄失敗那么課程接口就不要取跑了(可以這么寫)登錄失敗沒有sessionid
使用場景:前面一個動作失敗了后面的不要跑了,
前置條件,或者檢測到服務器python環境不是要求的,不要跑,
對版本有要求(系統環境,解釋器版本都可以作為代碼要不要執行的前置條件)
標簽只能到接口(方法層),不能指定到用例,用例是通過數據驅動來做的
四:定制執行還有一種寫法: 指名道姓----(下面的指令可以在cmd控制台命令里面直接執行)
只執行Test1的測試用例,執行命令:pytest -sq test_xt.py::Test1 py文件到類,類級別的(::級別關系)
只想執行Test2中test_3,執行命令:pytest -sq test_xt.py::Test2::test_3 py文件,類,接口方法
同時執行多個條件:執行命令:pytest -sq test_xt.py::Test1::test01 test_xt.py::Test2::test02
也可以根據用例名稱進行篩選-k,執行命令:pytest -sq -k 2 test_xt.py