在之前的文章裡我們已經學習了Python自帶測試框架UnitTest,但是UnitTest具有一定的侷限性
這篇文章裡我們來學習第三方框架Pytest,它在保留了UnitTest框架語法的基礎上有著更多的優化處理
下面我們將從以下角度來介紹Pytest:
下面我們首先來簡單介紹Pytest及相關內容
我們首先需要知道測試一般分為四個方面的測試:
而我們這篇文章主要針對的是單元測試:
最後我們需要明白單元測試框架的主要功能:
發現測試用例
執行測試用例
判斷測試結果
生成測試報告
下面我們來簡單介紹Pytest框架:
pytest是一個非常成熟的單元測試框架,經過多版本的迭代,主要優點在於靈活和簡單
pytest具有極強的相容性和生態環境,它可以結合selenium,requests,appium完成各種不同的自動化
pytest具有更好的頁面展示效果,它可以生成自定義allure報告以及和Jenkins持續整合
下面我們給出一些和Pytest框架可以很好聚合的框架型別:
我們可以採用一種比較簡便的方式來一次性下載這些框架:
# 首先我們需要將這些名稱全部放入一個txt檔案中,假設我們放在requestment.txt檔案中
# requestment.txt檔案
pytest-html
pytest-xdist
pytest-ordering
pytest-rerunfailures
# 我們只需要在pycharm的console中輸入指令下載該資料夾中全部內容即可
pip install -r requirements.txt
下面我們來介紹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通過韌體來實現前後置的方法:
# 我們通常採用前後置來做一些方法前後的操作
# 如果我們採用方法層的前後置,那麼它會在每個方法執行前後去執行該內容
# 如果我們採用類層的前後置,那麼它會在呼叫這個類內所有方法的前後去執行該內容,但是無論該類的方法執行多少次,它只會呼叫一次
# 例如我們做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來實現介面美化:
下面我們就來學習如何安裝使用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註解來進行資料驅動,下面我們來詳細講解一下:
# 格式:@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'])
這篇文章中詳細介紹了Python的第三方框架pytest,大家在閱讀完畢後就掌握了pytest的基本使用方法
下面給出我學習和書寫該篇文章的一些參考文章,大家也可以去查閱: