在之前的文章里我們已經學習了Python自帶測試框架UnitTest,但是UnitTest具有一定的局限性
這篇文章里我們來學習第三方框架Pytest,它在保留了UnitTest框架語法的基礎上有著更多的優化處理
下面我們將從以下角度來介紹Pytest:
Pytest基本介紹
Pytest基本使用
Pytest進階內容
Pytest基本介紹
下面我們首先來簡單介紹Pytest及相關內容
單元測試框架
我們首先需要知道測試一般分為四個方面的測試:
單元測試:稱模塊測試,針對軟件設計中的最小單位——程序模塊,進行正確性檢查的測試工作
集成測試:稱組裝測試,通常在單元測試的基礎上,將所有程序模塊進行有序的、遞增測試,重點測試不同模塊的接口部分
系統測試:將整個軟件系統看成一個整體進行測試,包括對功能、性能以及軟件所運行的軟硬件環境進行測試
驗收測試:指按照項目任務書或合同、供需雙方約定的驗收依據文檔進行的對整個系統的測試與評審,決定是否接收或拒收系統
而我們這篇文章主要針對的是單元測試:
Python:通常使用UnitTest和Pytest來進行單元測試自動化,但Pytest已經成為主流
Java:通常使用Testng和Junit來進行單元測試自動化,但Testng已經成為主流
最后我們需要明白單元測試框架的主要功能:
發現測試用例
執行測試用例
判斷測試結果
生成測試報告
框架基本介紹
下面我們來簡單介紹Pytest框架:
pytest是一個非常成熟的單元測試框架,經過多版本的迭代,主要優點在于靈活和簡單
pytest具有極強的兼容性和生態環境,它可以結合selenium,requests,appium完成各種不同的自動化
pytest具有更好的頁面展示效果,它可以生成自定義allure報告以及和Jenkins持續集成
下面我們給出一些和Pytest框架可以很好聚合的框架類型:
pytestpytest-html:主要用來生成html報告的插件
pytest-xdist:主要用來進行多線程運行的插件
pytest-ordering:主要用來改變用例的執行順序的插件
pytest-rerunfailres:主要用來失敗用例重跑的插件
allure-pytest:主要用來生成美觀自定義的allure報告
我們可以采用一種比較簡便的方式來一次性下載這些框架:
# 首先我們需要將這些名稱全部放入一個txt文件中,假設我們放在requestment.txt文件中 # requestment.txt文件 pytest-html pytest-xdist pytest-ordering pytest-rerunfailures # 我們只需要在pycharm的console中輸入指令下載該文件夾中全部內容即可 pip install -r requirements.txt
Pytest基本使用
下面我們來介紹Pytest的基本使用
Pytest默認測試用例
下面我們首先講解Pytest默認測試用例的格式:
# 首先我們的模塊名(文件名)通常被統一存放在一個testcases文件夾中,然后需要保證模塊名須以test_開頭或者_test結尾 # 例如我們下面的模塊名命名就是正確示例 test_demo1 demo2_test # 然后我們需要注意我們模塊中的測試類類名必須以Test開頭,并且不能帶有init方法 # 例如我們下面的類名命名就是正確示例 class TestDemo1: class TestLogin: # 最后我們需要注意我們測試類中的測試方法名(Case名)必須以test_開頭 # 例如我們下面的模塊名命名就是正確示例 test_demo1(self) test_demo2(self) # 我們給出一個測試用例例子: # 文件名為test_demo1 class TestDemo: def test_demo1(self): print("測試用例1") def test_demo2(self): print("測試用例2") # 當然我們上述的要求都不是必須相同的,在后續我們可以進行修改,我們將在下述講解執行方法時講解
然后我們再來講解一下Pytest的測試用例該如何執行:
# 首先我們講解一下全局配置文件pytest.ini # 我們可以在pytest.ini中進行一些屬性的配置來修改Pytest的默認屬性,我們需要在項目的根目錄下創建,名稱必須是pytest.ini 1 [pytest] 2 #參數 3 addopts = ‐vs # 這里指當默認使用指令時的一些輔助參數,我們后面會講解 4 testpaths = ./testcases# 這里指默認的執行路徑,它會默認執行該文件夾下所有的滿足條件的測試case 5 python_files = test_*.py# 這里就是前面我們所說的文件命名規則 6 python_classes = Test*# 這里就是前面我們所說的類名命名規則 7 python_functions = test_*# 這里就是前面我們所說的Case命名規則 8 #標記 9 markers =# 這里是冒煙規則,我們后面會講到 10 smoke:冒煙用例 11 product_manage:商品管理 # 然后我們首先來講采用console命令行執行Pytest的方法 # 最簡單的就是直接在console命令行輸入pytest,如果存在pytest.ini,它會根據文件內容進行執行;如果沒有就按照默認格式執行 # 但是我們可以通過一些參數來強化pytest參數指令 # -vs: -v輸出詳細信息 -s輸出調試信息 pytest -vs # -n: 多線程運行(前提安裝插件:pytest-xdist) pytest -vs -n=2 # --reruns num: 失敗重跑(前提安裝插件:pytest-rerunfailres) pytest -vs --reruns=2 # -x: 出現一個用例失敗則停止測試 pytest -vs -x # --maxfail: 出現幾個失敗才終止 pytest -vs --maxfail=2 # --html: 生成html的測試報告,后面 需要跟上所創建的文件位置及文件名稱(前提安裝插件:pytest-html) pytest -vs --html ./reports/result.html # -k: 運行測試用例名稱中包含某個字符串的測試用例,我們可以采用or表示或者,采用and表示都 # 采用or就表示:我們的運行用例名稱中包含or兩側的其中一個數據即可 # 采用and就表示:我們的運行用例名稱中包含and兩側的所有數據才滿足條件 pytest -vs -k "qiuluo" pytest -vs -k "qiuluo or weiliang" pytest -vs -k "qiuluo and weiliang" # -m:冒煙用例執行,后面需要跟一個冒煙名稱 # 我們在這里簡單介紹一下冒煙用例的執行方法,我們這里其實就是一個分組執行的方法 # 例如我們的用例劃分為user_manage用戶管理測試和product_manage商品管理測試,我們只希望執行其中一組測試 # 首先我們需要在他們的不同方法上進行@mark劃分,具體操作如下: class TestDemo: # 我們在Case上采用@pytest.mark. + 分組名稱,就相當于該方法被劃分為該分組中 # 注意:一個分組可以有多個方法,一個方法也可以被劃分到多個分組中 @pytest.mark.user_manage def test_demo1(self): print("user_manage_test1") @pytest.mark.product_manage def test_demo2(self): print("product_manage_test1") @pytest.mark.user_manage @pytest.mark.product_manage def test_demo3(self): print("manage_test1") # 我們在執行中只需要采用前面我們所說的-m + 分組名稱即可 pytest -vs -m user_manage # 這里插一句,我們在運行過程中可以采用拋出異常的方式來模擬測試失敗:raise Exception() 拋出異常 # 最后我們也可以采用main方法來執行pytest,同樣我們也可以使用參數來進行調節 if __name__ == '__main__': pytest.main() if __name__ == '__main__': pytest.main(["‐vs"])
最后我們插入一個簡單的案例跳過方法:
# pytest的跳過案例方法其實和unittest是完全相同的 # 我們只需要采用skip或skipif方法來指定參數并貼在方法上即可跳過 # @pytest.mark.skip(跳過原因) # @pytest.mark.skipif(跳過條件,跳過原因) # 我們給出一個示例 class TestDemo: workage2 = 5 workage3 = 20 @pytest.mark.skip(reason="無理由跳過") def test_demo1(self): print("我被跳過了") @pytest.mark.skipif(workage2<10,reason="工作經驗少于10年跳過") def test_demo2(self): print("由于經驗不足,我被跳過了") @pytest.mark.skipif(workage3<10,reason="工作經驗少于10年跳過") def test_demo3(self): print("由于經驗過關,我被執行了") def test_demo3(self): print("我沒有跳過條件,所以我被執行了")
Pytest前后置方法
首先我們需要先了解前后置是什么:
前后置就是針對不同層級方法執行前和執行后所需要執行的步驟進行封裝并執行
這個層級通常被劃分為:文件層,類層,方法層
首先我們先來介紹Pytest通過固件來實現前后置的方法:
# 我們通常采用前后置來做一些方法前后的操作 # 如果我們采用方法層的前后置,那么它會在每個方法執行前后去執行該內容 # 如果我們采用類層的前后置,那么它會在調用這個類內所有方法的前后去執行該內容,但是無論該類的方法執行多少次,它只會調用一次 # 例如我們做login測試時,我們只需要在開始測試時打開一次瀏覽器,然后在測試結束時關閉一次瀏覽器,那么我們就采用類的前后置 # 我們做login測試時,為了保證前置操作不對后續Case有影響,所以我們在執行方法前打開該網頁,執行方法后關閉該網頁,采用方法的前后置 # Pytest的固件前后置其實和unittest是基本相同的 # 首先是方法級別的固件前后置 # 它是在每個測試方法(用例代碼) 執行前后都會自動調用的結構 # 方法執行之前 def setUp(self): 每個測試方法執行之前都會執行 pass # 方法執行之后 def tearDown(self): 每個測試方法執行之后都會執行 pass # 然后是針對類級別的固件前后置 # 它是在每個測試類中所有方法執行前后 都會自動調用的結構(在整個類中執行之前或之后執行一次) # 需要注意:類級別的固件前后置, 是一個類方法 # 類中所有方法之前 @classmethod def setUpClass(cls): pass # 類中所有方法之后 @classmethod def tearDownClass(cls): pass # 最后是針對模塊級別的固件前后置 # 在每個代碼文件執行前后執行的代碼結構 # 需要注意:模塊級別的需要寫在類的外邊直接定義函數即可 # 代碼文件之前 def setUpModule(): pass # 代碼文件之后 def tearDownModule(): pass # 下面我們采用一個用戶賬戶登錄的用例來簡單展示一下固件前后置 import unittest class TestLogin(unittest.TestCase): # 在執行該類前所需要調用的方法 @classmethod def setUpClass(cls) -> None: print('------打開瀏覽器') # 在執行該類后所需要調用的方法 @classmethod def tearDownClass(cls) -> None: print('------關閉瀏覽器') # 每個測試方法執行之前都會先調用的方法 def setUp(self): print('輸入網址......') # 每個測試方法執行之后都會調用的方法 def tearDown(self) -> None: print('關閉當前頁面......') # 測試Case1 def test_1(self): print('輸入正確用戶名密碼驗證碼,點擊登錄 1') # 測試Case2 def test_2(self): print('輸入錯誤用戶名密碼驗證碼,點擊登錄 2')
然后我們還需要講解一下Fixtrue實現前后置的方法:
# 首先我們需要知道Fixtrue所實現的功能基本和固件所實現的功能是一樣的,但是會更加方便 # 首先我們給出Fixture的完整格式,然后我們再分開介紹各個參數 @pytest.fixture(scope=None,autouse=False,params=None,ids=None ,name=None) # scope:作用范圍 # 參數主要有三種:function函數,class類,package/session包 # function:在函數層面上執行前后置 # 我們通常采用yield進行前后置劃分,yield前是前置,yield后是后置 @pytest.fixture(scope="function") def exe_database_sql(): print("執行SQL查詢") yield print("關閉數據庫連接") # 我們還可以通過yield或return去返回一些參數在方法中使用 # 但是需要注意,yield返回參數后后置仍舊可以執行,但是return返回參數后后置操作無法執行 @pytest.fixture(scope="function") def exe_database_sql(): print("執行SQL查詢") yield "success" # return "success" 執行后無法執行后置操作 print("關閉數據庫連接") # 我們的方法在調用時,可以直接使用exe_database_sql表示返回信息進行輸出 def test_2(self,exe_database_sql): print(exe_database_sql) # class:在類之前和之后執行 @pytest.fixture(scope="class") def exe_database_sql(): print("執行SQL查詢") yield print("關閉數據庫連接") # package/session:在整個項目會話之前和之后執行 @pytest.fixture(scope="session") def exe_database_sql(): print("執行SQL查詢") yield print("關閉數據庫連接") # autouse:是否自動啟動 # 該參數默認為False,我們可以將其修改為True # 該參數的功能主要在判斷該固件是否在自定義范圍內可以自動啟動 # 若自動啟動,則所有方法在執行時都會自動執行該前后置;但若為False,則我們需要手動啟動 # 首先如果是自動啟動,則我們無需關心任何參數,我們的所有方法都會自動調用 @pytest.fixture(scope="function",autoues=True) def exe_database_sql(): print("執行SQL查詢") yield print("關閉數據庫連接") # 但若是關閉自動啟動,我們在不同的scope下有不同的調用方法 @pytest.fixture(scope="function",autoues=Flase) def exe_database_sql(): print("執行SQL查詢") yield print("關閉數據庫連接") # scope = function:我們需要在方法后加上該Fixture方法名 def test_2(self,exe_database_sql): print(exe_database_sql) # scope = class:我們需要在對應的類上添加@pytest.mark.usefixtures("exe_database_sql")裝飾器調用 @pytest.mark.usefixtures("exe_database_sql") class TestDemo: pass # scope = session:.一般會結合conftest.py文件來實現,我們后面再介紹 # 還需要注意autouse僅限于在自己的類中使用上述方法,如果要跨類使用,那么我們也需要在conftest.py中配置 # params:實現參數化配置 # 通常我們的腳本都是根據導出的yaml文件進行屬性填充,針對參數化我們后面再講,我們先將Fixture的參數化 # params通常后面跟上具體的數據(列表,元組等),然后我們在調用時有固定的寫法 # 首先我們需要在Fixture方法參數中定義一個request,然后使用request.param來使用我們傳遞的params數據 class TestDemo: def read_yaml(): return ["胡桃","胡桃寶寶","胡桃廚"] # 首先我們的參數需要獲取數據:params=read_yaml() @pytest.fixture(scope="function",autouse=False,params=read_yaml()) # 然后我們的Fixture方法需要一個request參數 def exe_database_sql(request): print("執行SQL查詢") # 我們通過request.param獲取數據,可以采用yield返回該數據 yield request.param print("關閉數據庫連接") # ids:參數別名id # 不能單獨使用,必須和params一起使用,作用是對參數起別名 # 我們在采用pytest進行測試數據輸出時會有對應的方法調用n次,該n次采用不同的params參數,這個ids就是修改了console控制臺展示數據 class TestDemo: def read_yaml(): return ["胡桃","胡桃寶寶","胡桃廚"] # 當我們書寫了ids,我們的控制輸出就不會再是上面的["胡桃","胡桃寶寶","胡桃廚"],而是我們所書寫的["1","2","3"] @pytest.fixture(scope="function",autouse=False,params=read_yaml(),ids=["1","2","3"]) def exe_database_sql(request): print("執行SQL查詢") # 我們通過request.param獲取數據,可以采用yield返回該數據 yield request.param print("關閉數據庫連接") # name:Fixture別名 # 作用是給fixtrue起別名,一旦使用了別名,那么fixtrue的名稱就不能再用了,只能用別名 class TestDemo: # 如果我們在這里使用到了別名 @pytest.fixture(scope="function",name="exe_datebase_sql_name") def exe_database_sql(request): print("執行SQL查詢") yield print("關閉數據庫連接") # 我們這里就需要使用別名進行操作,之前的名稱無法使用 def test_2(self,exe_datebase_sql_name): print(exe_database_sql)
接下來我們就將會講解到我們剛剛提到的conftest.py文件:
# 首先我們需要知道conftest.py文件的名字是固定形式,不可改變 # conftest.py文件主要就是用來存儲我們的Fixture,然后我們會根據該文件的不同位置來判斷可以使用的方法 # conftest可以在不同的目錄級別下創建,如果我們在根目錄下創建,那么所有case都會使用到該Fixture # 但是如果我們在testcases文件夾下的某個模塊文件下創建conftest.py,那么它的作用范圍就只包含在該目錄下 # 根目錄創建的conftest.py # 我們在該目錄下的conftest文件里寫的所有fixture可以在任意測試類下執行 import pytest @pytest.fixture(scope="function",name="exe_datebase_sql_name") def exe_database_sql(): print("全部方法運行前均可以執行") yield print("全部方法運行后均可以執行") # testcases文件下的所有測試類 # 這里需要注意:我們使用conftest下的Fixture時,不需要import導包就可以使用 import pytest class TestDemo1: # 測試Case1 def test_1(self,exe_datebase_sql_name): print('輸入正確用戶名密碼驗證碼,點擊登錄 1' + exe_datebase_sql_name) # testcases文件夾下的usercases文件夾下創建的conftest.py # 我們在該目錄下創建的conftest文件里寫的所有fixture僅可以在該目錄下的測試類中使用,在其他測試類中使用會出現報錯 import pytest @pytest.fixture(scope="function",name="usercases_fixture") def exe_database_sql(): print("usercases方法運行前均可以執行") yield print("usercases方法運行后均可以執行") # testcases文件下的usercases文件夾下的測試類 import pytest class TestUserCases1: # 測試Case1 def test_1(self,usercases_fixture): print('輸入正確用戶名密碼驗證碼,點擊登錄 1' + usercases_fixture) # 最后我們簡單給出一個前后置執行順序優先級: fixture_session > fixture_class > setup_class > fixture_function > setup
然后最后我們給出前后置執行的一個總體邏輯順序:
查詢當前目錄下的conftest.py文件
查詢當前目錄下的pytest.ini文件并找到測試用例的位置
查詢用例目錄下的conftest.py文件
查詢測試用例的py文件中是否有setup,teardown,setup_class,teardown_class
再根據pytest.ini文件的測試用例的規則去查找用例并執行
Pytest進階內容
最后我們再來講解一些pytest比較關鍵性的一些進階內容
Allure效果美化
我們在使用Pytest所生成的頁面往往不夠美觀且展示信息雜亂不好分析,所以我們通常搭載allure來實現界面美化:
Allure框架是一個靈活輕量級多語言測試報告工具
它不僅可以以WEB的方式展示簡介的測試結果,而且允許參與開發過程的每個人從日常執行的測試中最大限度的提取有用信息
下面我們就來學習如何安裝使用allure:
# 首先我們需要去下載在電腦上下載allure并配置好環境變量 # 我們這里給出官網下載地址:https://github.com/allure-framework/allure2/releases # 溫馨提醒:下載鏈接在github上,如果無法打開可以刷新重試或者使用加速器梯子等輔助工具 # 環境變量的配置只需要將bin文件所在目錄放在電腦的Path路徑下即可,這里不再展示 # 第二步我們需要在pycharm上下載allure-pytest插件(如果之前pip了那個整體文件,這里應該是已經下載過了) pip install allure-pytest # 第三步我們就可以直接來生成allure的測試結果展示界面了 # 1.我們通常首先需要生成一個allure臨時Json文件 # 我們通常會加上這么一串"‐‐alluredir=./temps ‐‐clean‐alluredir" # ‐‐alluredir = 文件生成地址 : 表示我們將allure臨時文件生成在我們所指定的相對臨時目錄下 # ‐‐clean‐alluredir : 由于每次都會生成大量文件,所以我們會在生成前清除當前目錄下的allure文件,保證我們數據都是最新數據 # 2.我們需要依靠臨時文件來生成allure.html網頁 # 我們通常在main方法中執行 if __name__ == '__main__': # 正常運行 pytest.main() # 休眠:主要為了JSON臨時文件的生成 time.sleep(3) # allure generate 固定語句 + allure臨時JSON文件目錄 + -o 輸出指令 + allure.html生成文件目錄 + --clean 清除舊數據 os.system("allure generate ./temps ‐o ./reports ‐‐clean")
Parametrize數據驅動
我們通常會采用Parametrize注解來進行數據驅動,下面我們來詳細講解一下:
# 格式:@pytest.mark.parametrize(參數名稱,參數值) # 意義:我們會將參數名稱作為id,然后根據參數值的個數去依次調用,存在n個參數值,我們將會調用n次case # 1.參數值為列表或元組時,參數名稱可以為一個 # 首先我們這里因為使用單個元素的列表(元組),我們的參數名可以為一個 @pytest.mark.parametrize('caseinfo',['胡桃','胡桃寶寶','芙芙','芙芙寶寶']) # 在方法參數里,我們需要調用parametrize的參數名稱caseinfo,需要保證一模一樣 def test_01_get_token(self,caseinfo): # 在這里我們可以借助參數名稱caseinfo來代替列表中的元素 # 列表中存在幾個,我們該方法將執行幾次,例如現在列表是四個元素,那么我們方法將會重復執行四次并每次按順序賦值不同的元素 print("獲取統一接口鑒權碼:"+caseinfo) # 2.參數值為列表的多個時,參數名稱可以為多個 # 這里我們列表中嵌套了一個列表,如果我們是單參數名稱,那么輸出時就會將第一個列表['胡桃廚','胡桃寶寶']輸出出去 # 但是如果我們是多參數名稱,系統會自動將第一個列表的元素分開賦值給arg1,arg2便于我們分開使用,個人還是比較推薦的 @pytest.mark.parametrize('arg1,arg2',[['胡桃廚','胡桃寶寶'],['芙芙廚','芙芙寶寶']]) # 注意:這里當然也需要和參數名稱對應!!! def test_01_get_token(self,arg1,arg2): print("獲取統一接口鑒權碼:"+str(arg1)+" "+str(arg2))
我們在進行數據驅動時通常會結合Yaml文件來進行數據獲取,這里我們簡單介紹一下Yaml文件:
# yaml是一種數據格式,擴展名可以是yaml,yml # 支持#注釋,通過縮進表示層級,區分大小寫,且yaml文件最后獲取的結果展示是一個字典列表格式 # yaml文件經常用于書寫配置,例如Java的Spring中的配置文件,而我們也經常采用yaml編寫自動化測試用例 # yaml文件通常會出現兩種格式 # 字典格式:如果我們正常書寫yaml文件,如下就是字典模式 name: 胡桃 # 列表模式:如果我們采用yaml中的列表,那么我們在py獲取時也將獲得列表 msjy: - name1: 胡桃 - name2: 芙芙 - ages1: 18 - ages2: 19 # 我們也可以利用這個特性,直接在yaml中做多個列表,來多次提取 - name:'xxx' age:18 - name:'xxx' age:20 # 我們這里首先給出一個解析yaml文件的示例函數: import os.path import yaml # 這里是獲取當前路徑,因為我們需要找到對應的yaml文件,那么具體路徑就需要我們進行拼接 def get_obj_path(): # 這里我們使用了Python的os類來進行當前路徑獲取,最后返回結果其實是一個String字符串 # 我們以'common'作為分界(common是當前文件夾的名稱,我們將該Str進行劃分,獲取前面的部分),獲取到前面的路徑部分來進行拼接 return os.path.dirname(__file__).split('common')[0] # 然后我們這里定義一個方法來解析yaml文件 def read_yaml(yamlPath): with open(get_obj_path() + yamlPath,mode = 'r',encoding = 'utf-8') as f: # 這里需要我們pip install pyyaml value = yaml.load(steam=f,Loader=yaml.FullLoader) return value # 然后我們這里采用一個main方法來執行上述用例(其實應該在其他測試類中執行) if __name__ = '__main__': # 調用read_yaml方法并給出yaml路徑 print(read_yaml('testcase/user_manage/get_token.yaml')) # 了解了所有東西之前我們就可以結合之前的Parametrize來進行操作: # 我們這里將所需要的數據變為read_yaml讀取的yaml文件內容 @pytest.mark.parametrize('caseinfo',read_yaml('testcase/user_manage/get_token.yaml')) def test_01_get_token(self,caseinfo): # 這里我們就可以獲取到yaml文件內容并輸出了 print("獲取統一接口鑒權碼:"+caseinfo) # 當然如果我們了解我們的yaml中擁有什么元素,我們還可以采用[]的方式具體表達出來 @pytest.mark.parametrize('caseinfo',read_yaml('testcase/user_manage/get_token.yaml')) def test_01_get_token(self,caseinfo): print("獲取統一接口鑒權碼:") # 這里我們可以直接獲取namekey對應的value print("caseinfo[name]:"+ caseinfo['name']) # 這里我們可以分別獲取request層下的method,url,data分別對應的value print("caseinfo[name]:"+ caseinfo['request']['method']) print("caseinfo[name]:"+ caseinfo['request']['url']) print("caseinfo[name]:"+ caseinfo['request']['data'])
編輯:黃飛
-
系統測試
+關注
關注
2文章
35瀏覽量
14826 -
集成測試
+關注
關注
0文章
25瀏覽量
8251 -
數據庫
+關注
關注
7文章
3842瀏覽量
64574 -
python
+關注
關注
56文章
4805瀏覽量
84929
原文標題:一篇文章帶你了解Python常用自動化測試框架——Pytest
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論