Python從無到有搭建介面(API)自動化測試框架
轉自:https://www.csdn.net/tags/MtTaMgwsMTg2MjI4LWJsb2cO0O0O.html
Python從無到有搭建介面(API)自動化測試框架 2021-01-03 23:51:03
-
目錄
1、前言
自動化測試,是測試道路上不可或缺的重要部分,現在有很多自動測試工具,基本可以滿足軟體市場的測試要求,但使用工具讓人知其然而不知其所以然,學會了也只是一個自動化測試工具人,所以學會自動化框架,是擺脫工具人、提升自己、加薪升職的必經之路;
天王劉德華說:學到了要教人。
這是一個已經學會了的人分享的一點知識,希望測試同胞們在前進路上路過我這篇部落格時,如得感覺有一點點的幫助,請留下您的一個贊。
觀看此文時,需要有入門級的Python程式碼基礎,如沒有,請先去找視訊教程學習一段時間。
本文所有講解程式碼與執行結果用截圖展示,這為了是讓您看著可以有個寫程式碼的過程,提升自己;當然如果不想寫,也可以只接跳轉每一小節末尾,整體程式碼展示,可以"使用程式設計師高階技能ctrl+c, ctrl+v"自行學習。
程式碼庫:魂尾/testApi
2、思路
1、搭建一個目錄框架
如下圖
common目錄裡內容含義
setApirequest.py 實現API介面請求的模組,實現傳送post/get等方法;
getCase.py 實現讀取data目錄裡的用例檔案資料的,設計執行格式,輸出JSON用例集;
getConfig.py 實現讀取config目錄下的配置檔案;
initPath.py 實現獲取框架檔案路徑,方便其他模組使用目錄方法;
log.py 實現日誌列印統一入口,將檔案輸出到log目錄裡;
operatorDB.py 實現讀寫資料的方法;
parameteriZation.py 實現資料引數化的實體類;
sendEmail.py\ SendMsg.py 實現實時傳送測試報告至郵件與企業微信的方法;
kemel目錄裡內容含義
methodFactory.py 實現各方法呼叫的統一入口;
commKeyworl.py 公共方法、主要是分裝所有底層模組的操作入口,並提供一些特殊性的公共方法;
testcase目錄裡內容含義
圖片裡目錄中沒有新增檔案。應該是這個檔案tsetCase.py,實現解析getCase.py裡輸出的JSON用例集,執行用例的,檢查執行結果的模組,因為Unitest庫的要求,此用例解析目錄與檔案必須以test開頭,當然含義也見名思義。
其他目錄
data用例檔案目錄,log輸出日誌目錄、report輸出測試報告目錄,library引入三方模組目錄(ddt資料驅動模組,HTMLRunner測試報告格式化輸出模組)
library引入兩個三方庫的下載路徑(重要檔案,不或或缺):
ddt:自動代測試資料驅動ddt.py-網際網路文件類資源-CSDN下載
HTMLRunner: HTMLTestRunnerNew.py針對《Python從無到有搭建介面(API)自動化測試框架》的測試報告,非此框架勿下載_htmltestrunnernew-網際網路文件類資源-CSDN下載
5、分層概念
一個軟體MCV的層次概念
M是底層,模型層,封裝功能的程式碼,屬於底層程式碼 。
C是中層,控制層,封裝所有底層的接入方法。
V是高層,會話層,執行執行等方法操作。
MVC的區分(分層概念)
common目錄下的模組都是M
kemel目錄下的模組是C
test_Case.py, testRun.py等執行模組是V
3、正文
一、路徑模組-initPath.py
1、瞭解如何獲取當前絕對路徑:
先了解python是如何取得當前目錄的,我們可以使用內建庫os來實現
首先我們得知道 __file__在python中代表當前檔案,
然後在菜鳥教程裡找到os.path的庫,知道獲取絕對路徑的方法 os.path.abspath(path)
然後開始在initPath.py寫程式碼列印一下os.path.abspath(__file__)方法
見下圖,打印出了initPath.py的絕對路徑。
2、獲取檔案路徑,不包含本身檔名
在菜鳥找到了os.path,dirname()方法
在initPath.py裡使用列印一下這個方法,然後我們希望是\的路徑,所以使用os.path.dirname(os.path.abspath(__file__))這個程式碼取路徑
然後我們再增加一層,os.path.dirname(os.path.dirname(os.path.abspath(__file__))),我們是要獲取工程根目錄路徑的。如下圖
3、拼接所有路徑
我們要拼接目錄,需要一個方法,進入菜鳥網,找到os.path.join(path1, path2),合成路徑方法
直接寫initPath.py的程式碼了,將專案下所有資料夾的路徑都定義好了。列印如下圖
initPath.py的程式碼段
import os #get project dir BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #get common dir COMMONDIR = os.path.join(BASEDIR, 'common') #get config dir CONFDIR = os.path.join(BASEDIR, 'config') #get data dir DATADIR = os.path.join(BASEDIR, 'data') #get library dir LIBDIR = os.path.join(BASEDIR, 'library') #get log dir LOGDIR = os.path.join(BASEDIR, 'log') #get report dir REPORTDIR = os.path.join(BASEDIR, 'report') #get testcaset dir CASEDIR = os.path.join(BASEDIR, 'testcases')
二、配置檔案模組-getConfig.py
1、我們先了解一下配置檔案
在計算機領域,配置檔案:是一種計算機檔案,可以為一些計算機程式配置引數和初始設定。
具體檔案型別與相關知識點不多說,我們這裡使用一ini配置檔案,內容格式如下
[about]
aaa = bbb
ccc = ddd
其實about是節點,aaa = bbb是引數,key = value的意思
所以在config/baseCon.ini下配置新增一些配置,如下圖
2、讀取baseCon.ini裡的配置項
獲取配置檔案路徑:
讀取配置檔案之前,我們得先得到檔案baseCon.ini的絕對路徑,
先引用已經定義好的config的絕對路徑,然後使用os.path.join(path1, path2)方法將baseCon.ini的絕對路徑生成,具體程式碼如下圖
瞭解configparser
ConfigParser 是用來讀取配置檔案的包。三方庫,所以需要通過命令 pip install configparser 來下載。
在程式碼裡直接匯入ConfigParser類,然後建立其對像,呼叫方法read(),讀取配置檔案,具體程式碼如下圖
仔細學習上面的路子,下面就開始著手封裝configparser了,這個庫已經很完美了,不過我們後續讀取配置檔案時需要將讀取檔案這個步驟也省略,所以稍加封裝。
封裝configparser配置檔案讀取
封裝的程式碼執行如下圖
nyCof.saveData()方法添加了配置項,寫到了baseCon.ini裡面
getConfig.py的程式碼段:
import os from common.initPath import CONFDIR from configparser import ConfigParser # conPath = os.path.join(CONFDIR, 'baseCon.ini') # print(conPath) # cnf = ConfigParser() # cnf.read(conPath, encoding='utf-8') #第一個引數是檔案路徑,第二個引數是讀取的編碼 # # print('baseCon.ini裡所有節點{}'.format(cnf.sections())) #列印所有節點名稱 # print('db下的所有key:{}'.format(cnf.options('db'))) #列印db節點下的所有key # print('db下的所有Item:{}'.format(cnf.items('db'))) #列印db節點下的所有item # print('db下的host的value:{}'.format(cnf.get('db', 'host'))) #列印某個節點的某個value """ 定義Config繼續ConfigParser """ class Config(ConfigParser): def __init__(self): """ 初始化 將配置檔案讀取出來 super(). 呼叫父類 """ self.conf_name = os.path.join(CONFDIR, 'baseCon.ini') super().__init__() super().read(self.conf_name, encoding='utf-8') def getAllsections(self): """ :return: 返回所有的節點名稱 """ return super().sections() def getOptions(self, sectioName): """ :param sectioName: 節點名稱 :return: 返回節點所有的key """ return super().options(sectioName) def getItems(self, sectioName): """ :param sectioName: 節點名稱 :return: 返回節點的所有item """ return super().items(sectioName) def getValue(self, sectioName, key): """ :param sectioName: 節點的名稱 :param key: key名稱 :return: 返回sectioName下key 的value """ return super().get(sectioName, key) def saveData(self, sectioName, key, value): """ 新增配置 :param sectioName: 節點名稱 :param key: key名 :param value: 值 :return: """ super().set(section=sectioName, option=key, value=value) super().write(fp=open(self.conf_name, 'w')) myCof = Config() #print(myCof.getAllsections()) #print(myCof.getOptions('db')) #print(myCof.getItems('db')) #print(myCof.getValue('db', 'host')) #myCof.saveData('db', 'newKey', 'newValue')
三、讀取用例模組-getCase.py
讀取用例是框架的比較重要,與獨立的模組,不過讀取用例前,我們先要設計用例檔案與使用者格式,一般我們可以將用例放在excel裡,或者建立一個mysql資料庫,將excel裡的資料匯入到裡面,但後者比較麻煩,所以本文只接讀取excel檔案裡的用例
1、設計用例檔案與用例格式
在data目錄裡新增一個testcase.xlsx的excel檔案,目錄如下圖:
開啟testcase.xlsx,在sheet1裡設計用例表頭,如下圖:
用例欄位的含義:
case_id:id,自己設定數字,只為了輸出報告時識別用例的位置
api:url裡的介面路由,也是為了輸出報告時識別用例的正確性
title: 可以是用例的中文標題,也可以自定義方法中引數化接收資料的變數,格式可以是,abc或${abc}
method: 請求型別 post/get ,或者自定義的方法名稱
url: 介面請求中的url,可以被引數化
headers: 介面請求中的headers, 可以被引數化
data: 介面請求中的data, 可以被引數化
checkey:用例檢查點的檢查方法
expected: 用例檢查點的檢查值
Test_result: 用例執行完成後,回寫給用例的狀態,pass/fail,不過我一般不用。
用例示例如下圖:
2、設計用例集JSON格式
格式如下:
[ #用例集 { #第一條用例 key1: value1, #用例裡的欄位與欄位值 key2: value2 ... }, { #第二條用例 key1:value1, #用例裡的欄位與欄位 key2: value2 ... }, .... ]
根據用例excel檔案,具體的JSON用例串應該下面是這樣子的,前面欄位是excel列表資料,最後一個是sheet_name是表示了sheet頁名稱。一般介面用例按模組編寫,一個模組一個sheet頁,以此欄位區分,為後面輸出測試報告做準備。
[
{
'case_id':1,
'api': 'publickey',
'title':'url',
'method':'設定變數',
'url':'https://www.adbcde.com.cn/',
'headers':'Host:www.adbcde.com.cn',
'data':'userToken=16029513602050&loginpassword=qwert12345&loginphone=15361828291',
'checkey':'結果包含',
'expected':'"return_code": "SUCCESS"',
'Test result':None,
'sheet_name':'sheet1',
},
{
......
}
.....
]
3、openpyxl庫學習
讀寫excel的庫有很多,因為用例要求不高,所以選擇openpyxl庫來封裝用例程式碼
安裝使用命令 pip install openpyxl
開始寫程式碼
操作excel用例之前,要得到檔案的絕對路徑
獲取絕對路徑成功,接下來開始讀取excel裡的檔案了,
讀取excel檔案內容
步聚如下圖,1、先開啟excel檔案,2、遍歷一下sheet頁輸出名稱,3遍歷輸出Sheet1裡的資料,這三個寫後,就表名,excel可以讀取成功了。openpyxl也學完了
4、封裝用例類
我定義的用例是一個excel檔案一個專案,在工作中,應該不止一個專案,所以有可能在data目錄裡有多個excel檔案,我們需要執行哪個呢,所以此能用到前面寫的配置檔案,在裡面加一個case節點,增加執行用例檔名稱
一個用例檔案中肯定有很多註釋的用例,所以定義一個 # 來區分註釋用例。兩個定義如下圖
封裝程式碼無法載圖全部,先看執行結果,後面讀取用例只需要兩行程式碼就可將用例JSON讀取出來
getCase.py的程式碼段
import os import openpyxl from common.initPath import DATADIR from common.getConfig import myCof # #拼接用例檔案絕對路徑 # caseFile = os.path.join(DATADIR, 'testcase.xlsx') # print(caseFile) # #讀取excel檔案 # xl = openpyxl.open(filename=caseFile) # #列印caseFile裡的sheet頁名稱 # print('列印所有sheet頁') # for sheet in xl: # print(sheet.title) # #列印excel裡的所有的行欄位資料 # print('列印Sheet1裡的所有的行欄位資料') # sh = xl['Sheet1'] # data = list(sh.rows) # for da in data: # for k in da: # print(k.value) class Getcase(object): def __init__(self, sheet_name=None): """ 初始化檔名稱,sheet名稱,excel對像 :param sheet_name: 傳入的sheet名稱 ,可以為空。 """ filename = myCof.getValue('case', 'testCase') self.note = myCof.getValue('identifier', 'note') self.caseFile = os.path.join(DATADIR, filename) self.sheet_name = sheet_name self.wb = None def openexcel(self): """ 開啟excel檔案 如果sheet名稱不為空,定位到對應sheet頁 :return: """ self.wb = openpyxl.open(self.caseFile) if self.sheet_name is not None: self.sh = self.wb[self.sheet_name] def read_excels(self): """ 格式化用例集 用例格式JSON見上面的前面的描述 過濾掉#註釋的用例 :return: """ if self.wb is None: self.openexcel() datas = list(self.sh.rows) title = [i.value for i in datas[0]] cases = [] for i in datas[1:]: data = [k.value for k in i] case = dict(zip(title, data)) #將資料格式化中JSON串 try: if str(case['case_id'])[0] is not self.note: # 過濾掉note符號開頭的用例,註釋掉不收集、不執行 case['sheet'] = self.sh.title cases.append(case) except KeyError: cases.append(case) return cases def read_all_excels(self): """ 遍歷所有的sheet頁 取得所有用例集,再格式下一次, 過濾掉#註釋的sheet頁 :return: """ self.openexcel() cases = [] for sheet in self.wb: if sheet.title[0] is not self.note: # 過濾掉note符號開頭的sheet頁,註釋掉的不收集,不執行 self.sh = sheet cases += self.read_excels() return cases def write_excels(self, rows, column, value): """ 回寫用例欄位 :param rows: :param column: :param value: :return: """ self.openexcel() self.sh.cell(row=rows, column=column, value=value) self.wb.save(self.caseFile) # readExce = Getcase() # print(readExce.read_all_excels())
四、資料庫操作模組-operatorDB.py
1、pymysql安裝
安裝pymysql使用命令pip install pymysql
2、pymysql學習
學習之前,我們先把連線資料庫的相關配置引數,在配置檔案中取出來
如下圖,先匯入myCof對像,使用getValue取出對應該的配置引數列印成功
pymysql已經匯入成功了,連線資料引數也取出來了,接下來開始連線資料,執行SQL語句
連線資料庫
如果沒連線成功會丟擲異常,所以此時需要 try ... except.....來catch異常,打印出來,下圖為連線資料庫超時,因為還沒起Mysql的服務。
這裡我們得先配置好一個mysql伺服器,方便除錯,不懂的可以在網上找教程學習學習,當然直接用公司的測試環境也行,省時少力。
開啟mysql服務後,執行,會報一個錯,我們將port引數強轉化為int型別,他不能為str型別。
強轉後,再執行一次,連線成功了
執行SQL
執行SQL語句,我們需要遊標,先獲取遊標,再使用遊標執行SQL,一次通過
列印SQL查詢的資料
pymysql學習完成,接下來可以封裝了
3、封裝資料操作
封裝的邏輯是,將連線作一個方法,執行SQL寫一個方法,關閉連線寫一個方法
先看封裝好後的執行結果,只需要寫四行程式碼就可以執行SQL語句了。
opeartorDB.py的程式碼段:
import pymysql from common.getConfig import myCof # host = myCof.getValue('db', 'host') # port = int(myCof.getValue('db', 'port')) # user = myCof.getValue('db', 'user') # pwd = myCof.getValue('db', 'pwd') # database = myCof.getValue('db', 'database') # charset = myCof.getValue('db', 'charset') # try: # #連線資料庫 # db = pymysql.connect(host=host, port=port, user=user, password=pwd, database=database, charset=charset) # #獲取遊標 # cursor = db.cursor() # #執行SQL # cursor.execute("select * from Student where SName = '林小六';") # #獲取查詢結果 # result = cursor.fetchall() # #列印查詢結果 # print(result) # print('執行成功') # except Exception as e: # print('連線失敗,原因:{}'.format(str(e))) """ 封裝mysql操作 """ class OpeartorDB(object): def __init__(self): """ 初始化方法,習慣性留著 """ pass def connectDB(self): """ 連線資料庫 :return: 返回成功失敗,原因 """ host = myCof.getValue('db', 'host') port = myCof.getValue('db', 'port') user = myCof.getValue('db', 'user') pwd = myCof.getValue('db', 'pwd') database = myCof.getValue('db', 'database') charset = myCof.getValue('db', 'charset') try: self.db = pymysql.connect(host=host, port=int(port), user=user, password=pwd, database=database, charset=charset) return True, '連線資料成功' except Exception as e: return False, '連線資料失敗【' + str(e) + '】' def closeDB(self): """ 關閉資料連線,不關閉會導致資料連線數不能釋放,影響資料庫效能 :return: """ self.db.close() def excetSql(self, enpsql): """ 執行sql方法, :param enpsql: 傳入的sql語句 :return: 返回成功與執行結果 或 失敗與失敗原因 """ isOK, result = self.connectDB() if isOK is False: return isOK, result try: cursor = self.db.cursor() cursor.execute(enpsql) res = cursor.fetchone() #為了自動化測試的速度,一般場景所以只取一條資料 if res is not None and 'select' in enpsql.lower(): #判斷是不是查詢, des = cursor.description[0] result = dict(zip(des, res)) #將返回資料格式化成JSON串 elif res is None and ('insert' in enpsql.lower() or 'update' in enpsql.lower()): #判斷是不是插入或者更新資料 self.db.commit() #提交資料操作,不然插入或者更新,資料只會更新在快取,沒正式落庫 result = '' #操作資料,不需要返回資料 cursor.close() #關閉遊標 self.closeDB() #關閉資料連線 return True, result except Exception as e: return False, 'SQL執行失敗,原因:[' + str(e) + ']' # sql = 'select * from Student' # oper = OpeartorDB() # isOK, result = oper.excetSql(sql) # print(result)
五、日誌模組-log.py
1、logging學習
logging是python的基礎庫,不需要下載,直接匯入可用
日誌有五個等級,自動測試一般INFO等都列印,所以我們在配置檔案裡的加上日誌引數配置
[log]
level = INFO
列印日誌
編寫程式碼,先獲取日誌等級配置,然後設定日誌等級,初始化日誌對像,列印日誌,因為日誌等是INFO,所以debug的日誌不會列印,程式碼如下圖
設定日誌格式
格式設定如下圖
將日誌輸出到檔案
日誌檔案存放在log目錄下,所以先獲取匯入目錄與os
設計日誌檔案隔一天時間,日誌就另新增一個,保留十五天,所以需要匯入logging裡的一個方法TimedRotatingFileHandler、
from logging.handlers import TimedRotatingFileHandler #匯入的方法
程式碼如下圖
執行後,輸出了testReport檔案,裡面列印了執行日誌
logging基本學習完成 ,再簡單封裝一下
2、日誌模組封裝
封裝日誌這一塊,不需要建立對像,因為他本身需要返回一個logging的物件,物件操作物件,彆扭,所以在Log類裡直接封裝一個靜態的方法,可以直接類呼叫方法返回一個logging物件。
調式執行結果與上面一致,但不截圖了,直接上程式碼
log.py程式碼段
import os import logging from common.getConfig import myCof from common.initPath import LOGDIR from logging.handlers import TimedRotatingFileHandler # # 獲取日誌等配置引數 # level = myCof.getValue('log', 'level') # # 設定日誌格式,%(asctime)s表示時間,%(name)s表示傳入的標識名,%(levelname)s表示日誌等級,%(message)s表示日誌訊息 # format = '%(asctime)s - %(name)s-%(levelname)s: %(message)s' # # 設定日誌基礎等級, 設定 # logging.basicConfig(level=level, format=format) # # 初始化日誌對像,Hunwei是name # mylog = logging.getLogger('Hunwei') # #拼接日誌目錄 # log_path = os.path.join(LOGDIR, 'testReport') # #生成檔案控制代碼,filename是檔案路徑,when表是時間D表示天,backuCount=15目錄下最多15個日誌檔案,enccoding='utf-8'日誌字元格式 # fh = TimedRotatingFileHandler(filename=log_path, when="D", backupCount=15, encoding='utf-8') # #設定歷史日誌檔名稱的格式,會自動按照某天生成對應的日誌 # fh.suffix = "%Y-%m-%d.log" # #設定檔案輸出的日誌等級 # fh.setLevel(level) # #設定檔案輸出的日誌格式 # fh.setFormatter(logging.Formatter("%(asctime)s - %(name)s-%(levelname)s: %(message)s")) # #將檔案控制代碼加入日誌物件 # mylog.addHandler(fh) # # mylog.debug('debug') # mylog.info('info') # mylog.warn('warm') # mylog.error('error') # mylog.fatal('fatal') class Log(object): @staticmethod def getMylog(): # 獲取日誌等配置引數 level = myCof.getValue('log', 'level') # 設定日誌格式,%(asctime)s表示時間,%(name)s表示傳入的標識名,%(levelname)s表示日誌等級,%(message)s表示日誌訊息 format = '%(asctime)s - %(name)s-%(levelname)s: %(message)s' # 設定日誌基礎等級, 設定 logging.basicConfig(level=level, format=format) # 初始化日誌對像,Hunwei是name mylog = logging.getLogger('Hunwei') # 拼接日誌目錄 log_path = os.path.join(LOGDIR, 'testReport') # 生成檔案控制代碼,filename是檔案路徑,when表是時間D表示天,backuCount=15目錄下最多15個日誌檔案,enccoding='utf-8'日誌字元格式 fh = TimedRotatingFileHandler(filename=log_path, when="D", backupCount=15, encoding='utf-8') # 設定歷史日誌檔名稱的格式,會自動按照某天生成對應的日誌 fh.suffix = "%Y-%m-%d.log" # 設定檔案輸出的日誌等級 fh.setLevel(level) # 設定檔案輸出的日誌格式 fh.setFormatter(logging.Formatter("%(asctime)s - %(name)s-%(levelname)s: %(message)s")) # 將檔案控制代碼加入日誌物件 mylog.addHandler(fh) #返回logging對像 return mylog #調式程式碼 mylog = Log.getMylog() # mylog.debug('debug') # mylog.info('info') # mylog.warn('warm') # mylog.error('error') # mylog.fatal('fatal')
六、郵件模組-sendEmail.py
1、開啟郵箱的SMTP服務
email郵箱提供商都可以開啟smtp服務的,如果知道什麼是smtp並知道設定的朋友可以略過這一段
以qq郵箱為例
進入qqmail.com登入郵箱,找到【設定】-【賬戶】,點選POP3/SMTP 開啟(下圖示記的有誤,別被誤導了哈)
按描述傳送簡訊
開啟之後,我們會得到一個金鑰,好好儲存,
2、學習使用smtplib庫傳送帶附件的郵件
郵件引數新增到配置檔案
host 是郵件伺服器,騰訊的是smtp.qq.com,
port 是郵件伺服器埠,開通smtp時,騰訊會郵件告之埠號
user是郵箱、pwd是開通smtp時得到的金鑰
from_addr 是傳送郵箱地址,與user是同一個
to_addr是收件箱,可以做成逗號分隔
連線smtp伺服器
一般連線啥伺服器沒成功都會拋異常呀,所以用一下try,新建一個smtplib的對像,帶上伺服器與埠,然後使用使用者名稱密碼連線
傳送郵件
傳送郵件之前需要構建一個郵件內容,所以所以email庫,可以通過pip install email下載,使用
先構建一個純文字的內容 ,所以匯入 MIMEText,
下面是構建郵件內容與傳送成功的截圖,msg訊息休是郵件內容 ,裡面需要文字,傳送人,收件人,郵件主題等引數
傳送成功後,進入郵箱檢視郵件
郵件傳送成功後了,基礎的已經學會了,還有兩種郵件型別、MIMEMultipart多媒體內型,MIMEApplication附件內型,不多贅述,看後面封裝程式碼即可通明
3、封裝程式碼
封裝程式碼前呢,先知道了一般的自動化報告是html格式的,這裡我拿了以前的測試放在工程report目錄下,方便使用,如下圖
封裝郵件模組後,兩行程式碼即可傳送測試報告。
sendEmail.py的程式碼段
import os import smtplib from common.getConfig import myCof from email.mime.text import MIMEText #匯入純文字格式 from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from common.initPath import REPORTDIR # host = myCof.getValue('email', 'host') # port = int(myCof.getValue('email', 'port')) # user = myCof.getValue('email', 'user') # pwd = myCof.getValue('email', 'pwd') # to_addr = myCof.getValue('email', 'to_addr') # #定義純文字訊息 ,From定義發件人, To定義收件人, Subject定義郵件標題 # msg = MIMEText('hello,send by python_test...','plain','utf-8') # msg['From'] = user # msg['To'] = to_addr # msg['Subject'] = '測試郵件傳送' # try: # #連線smtp服務對話,建立對像 # smtp = smtplib.SMTP_SSL(host=host, port=port) # #登入伺服器 # smtp.login(user=user, password=pwd) # # 傳送郵件 # smtp.sendmail(from_addr=user, to_addrs=to_addr, msg=msg.as_string()) # # 結束與伺服器的對話 # smtp.quit() # print('傳送郵件成功') # except Exception as e: # print('傳送郵件失敗,原因:{}'.format(str(e))) class SendMail(object): def __init__(self): """ 初始化檔案路徑與相關配置 """ all_path = [] #獲取測試報告目錄下的報告檔名稱 for maindir, subdir, file_list in os.walk(REPORTDIR): pass #拼接檔案絕對路徑 for filename in file_list: all_path.append(os.path.join(REPORTDIR, filename)) self.filename = all_path[0] self.host = myCof.get('email', 'host') self.port = myCof.get('email', 'port') self.user = myCof.get('email', 'user') self.pwd = myCof.get('email', 'pwd') self.from_addr = myCof.get('email', 'from_addr') self.to_addr = myCof.get('email', 'to_addr') def get_email_host_smtp(self): """ 連線stmp伺服器 :return: """ try: self.smtp = smtplib.SMTP_SSL(host=self.host, port=self.port) self.smtp.login(user=self.user, password=self.pwd) return True, '連線成功' except Exception as e: return False, '連線郵箱伺服器失敗,原因:' + str(e) def made_msg(self): """ 構建一封郵件 :return: """ # 新增一個多元件郵件 self.msg = MIMEMultipart() with open(self.filename, 'rb') as f: content = f.read() # 建立文字內容 text_msg = MIMEText(content, _subtype='html', _charset='utf8') # 新增到多元件的郵件中 self.msg.attach(text_msg) # 建立郵件的附件 report_file = MIMEApplication(content) report_file.add_header('Content-Disposition', 'attachment', filename=str.split(self.filename, '\\').pop()) self.msg.attach(report_file) # 主題 self.msg['subject'] = '自動化測試報告' # 發件人 self.msg['From'] = self.from_addr # 收件人 self.msg['To'] = self.to_addr def send_email(self): """ 傳送郵件 :return: """ isOK, result = self.get_email_host_smtp() if isOK: self.made_msg() self.smtp.send_message(self.msg, from_addr=self.from_addr, to_addrs=self.to_addr) else: return isOK, result # abc = SendMail() # abc.send_email()
七、訊息模組-sendMsg.py
1、建立企業微信應用
在企業微信建立一個應用,為接收訊息的載體,新增相關人員
在Python中實現得到企業微信應用token,
在企業微信官網建立一個公司,或者用公司的企業微訊號,獲取企業微信的企業ID
建立應用
得到AgentId、Secret
進入新建的應用詳情頁面,可以得到這兩個欄位
1、學習傳送企業微信訊息
資深開發者直接會找企業微信的API開發指南,應該知道怎麼封裝了。
先將建立應該時得到的三個引數配置到baseCon.ini檔案裡
獲取企業微信的token
拼接得獲取token的API url,corpid與corpsecret欄位為引數,用requests.get()方法請求,從結果集中解析出token欄位值
傳送訊息
傳送訊息的程式碼如下圖,先拼接傳送訊息的api的url,需要新增上面得到token值,再構造一個訊息,轉換成bytes格式的訊息休,使用requests.post傳送訊息
企業微信收到了訊息,如下圖
2、程式碼封裝
封裝完成後,只需要兩行程式碼就可以傳送訊息了
sendMsg.py的程式碼段
import requests import json from common.getConfig import myCof # # 獲取企業微信的引數 # corpid = myCof.get('wechat', 'corpid') # corpsecret = myCof.get('wechat', 'corpsecret') # agentid = myCof.get('wechat', 'agentid') # # 拼接獲取token的API # url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' + corpid + '&corpsecret=' + corpsecret # # 使用requests請求API,轉為JSON格式 # response = requests.get(url) # res = response.json() # #獲取token列印 # token = res['access_token'] # print(token) # # 拼接傳送訊息的api # url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + token # # 構建一個訊息JSON串 # jsonmsg = { # "touser" : "@all", # "msgtype" : "text", # "agentid" : agentid, # "text" : { # "content" : "API介面從無到有" # }, # "safe":0 # } # # 將JSON轉成str,再轉成bytes格式的訊息休 # data = (bytes(json.dumps(jsonmsg), 'utf-8')) # # 使用requests post傳送訊息 # requests.post(url, data, verify=False) class SendMsg(object): def __init__(self): self.corpid = myCof.get('wechat', 'corpid') self.corpsecret = myCof.get('wechat', 'corpsecret') self.agentid = myCof.get('wechat', 'agentid') def getToken(self): if self.corpid is None or self.corpsecret is None: return False, '企業微信相關資訊未配置' url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' + self.corpid + '&corpsecret=' + self.corpsecret response = requests.get(url) res = response.json() self.token = res['access_token'] return True, '企業微信token獲取成功' def sendMsg(self, msg): _isOK, result = self.getToken() if _isOK: url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + self.token jsonmsg = { "touser" : "@all", "msgtype" : "text", "agentid" : self.agentid, "text" : { "content" : msg }, "safe":0 } data = (bytes(json.dumps(jsonmsg), 'utf-8')) requests.post(url, data, verify=False) else: print(result) # wechatMsg = SendMsg() # wechatMsg.sendMsg('API介面從無到有')
八、變數引數化模組-parameteriZation.py
前面七小節已經將自動化測試框架的周邊全部弄完了,接下來便開始寫核心模組了。
自動化測試工具有很多變數可以配置,分為兩大類,系統環境變數(在執行之前,人為配置好的變數,執行完還存在),臨時變數(在執行過程引數化的變數,執行完成消失,等待下次引數化)
1、系統環境變數
這個很簡單 ,我們直接寫在配置檔案即可,比如我們需要一個賬號與密碼,將其設定為系統環境變數,直接在bascCon.ini裡新增
然後我們匯入 myCon,使用getValue方法就可以取到變數的引數了,
這還是第一步,我們需要設計變數的格式,比如 {aaa}, ${aaa},前者使用{}來標識aaa需要引數化,後者用${}來標識aaa需要引數化,
那就將${}定義為變數的識別符號
取變數名
那麼我們將如何來取變數名呢,比如,${phone},直接使用是取不到手機號碼的,需要將phone取出來。此時我們可以使用正則表示式庫,re來處理
正則表示式很強大,規則也很多,在這裡不做贅述。re的話是Python的基礎庫,所以可以在菜鳥站上搜索到教程,我們要用到的方法就這兩個
程式碼如下與執行結果見下圖
將變數引數化
就是在上面程式碼里加上獲取配置引數即可,程式碼與執行結果見下圖:變數已經引數化了。
2、臨時變數
臨時變數,這是個檢驗Python程式碼基礎的活兒,面向物件,屬性,setattr ,getattr等知識點。如果懂了我們只需要在上加幾行程式碼就成了。
先定義一個空類。不給屬性與方法,在執行程式碼的過程中使用setattr給這個空類新增屬性與值,這個屬性即 臨時變數,如果想呼叫,即可用getattr取屬性的值,進行引數化
程式碼如下與執行結果如下
臨時變數引數化
設定好臨時變數,引數化過程與系統環境變數的差不多,區別是將myCon.getValue(‘par’, key) 改成getattr(Paramte, key)
程式碼與執行結果如下圖:
3、程式碼封裝
parameteriZation.py的程式碼段
import re from common.getConfig import myCof # phone = myCof.getValue('par','phone') # print(phone) # pwd = myCof.getValue('par', 'pwd') # print(pwd) #定義一個字串,裡面有兩個變數 # data = '{PHONE : ${phone}, PASSWORD: ${pwd}}' # #定義正則匹配規則 # ru = r'\${(.*?)}' # #迴圈取變數名稱 # while re.search(ru, data): # #取值第一個變數 # res = re.search(ru, data) # #取出名稱 # key = res.group(1) # #取出環境變數 # value = myCof.getValue('par', key) # #替換變數 # data = re.sub(ru, value, data, 1) # #列印替換後的字串 # print(data) # 給臨時變數的空類 # class Paramete(): # pass # # 設定臨時變數 # setattr(Paramete, 'phone', '15381819299') # setattr(Paramete, 'pwd', '654321') # # 直接呼叫取值列印 # # print('直接列印:' + Paramete().phone) # # 通過getattr列印 # # print('getattr列印:' + getattr(Paramete, 'phone')) # data = '{PHONE : ${phone}, PASSWORD: ${pwd}}' # #定義正則匹配規則 # ru = r'\${(.*?)}' # #迴圈取變數名稱 # while re.search(ru, data): # #取值第一個變數 # res = re.search(ru, data) # #取出名稱 # key = res.group(1) # #取出環境變數 # value = getattr(Paramete, key) # #替換變數 # data = re.sub(ru, value, data, 1) # # print(data) class Paramete: pass def replace_data(data): """ 替換變數 :param data: :return: """ ru = r'\${(.*?)}' while re.search(ru, data): res = re.search(ru, data) item = res.group() keys = res.group(1) # 先找系統環境變數,如果有則替換;如果沒有則找臨時變數 try: value = myCof.get('test_data', keys) except Exception as e: value = getattr(Paramete, keys).encode('utf-8').decode('unicode_escape') finally: data = re.sub(ru, value, data, 1) return data def analyzing_param(param): """ ${abc}取出abc :param param: :return: """ ru = r'\${(.*?)}' if re.search(ru, param): return re.findall(ru, param)[0] return param # print(replace_data('${phone}, ${pwd}'))
九、API請求模組-sendApirequests.py
1、requests庫下載
第三方庫,所以需要用命令:pip install requests 下載
2、requests庫學習
requests的請求型別
常用的請求型別都在下圖
目前主要的請求是get與post
requests.get(url=‘請求api的url’, params=‘get請求的引數,可以為空’, headers=‘請求頭,如果介面沒有校驗,可以為空’)
requests.post(url=‘請求api的url’, json=‘如果json引數,使用json欄位’, data=‘如果是表單格式,使用data引數’, files=‘當資料為檔案時,使用file引數’, headers=‘請求頭,如果介面沒有校驗,可以為空’)
post裡的可以傳json、data、file三種引數,但三個只能傳一個。
3、api請求封裝
sendApirequest.py程式碼段
這個檔案在kemel目錄下面
class SendApirequests(object): def __init__(self): self.session = requests.session() def request_Obj(self, method, url, params=None, data=None, json=None, files=None, headers=None,): opetype = str.lower(method) if opetype == 'get': response = requests.get(url=url, params=params, headers=headers) elif opetype == 'post': response = requests.post(url=url, json=json, data=data, files=files, headers=headers) return response
封裝程式碼呼叫-get
封裝程式碼呼叫-post
十、公共方法的模組(核心)-commKeyword.py
1、公共方法理念
一個軟體MCV的層次概念
M是底層,模型層,前面封裝的程式碼都是這種模組,屬於底層程式碼 。
C是中層,控制層,封裝所有底層的接入方法。
V是高層,會話層,介面也操作提供給使用者使用。
MVC的區分(分層概念)
common目錄下的模組都是M
kemel目錄下的模組是C
解析用例與執行檔案是V
公共方法、主要是分裝所有底層模組的操作入口,並提供一些特殊性的公共方法
什麼是特殊的公共方法呢?
比如:自動生成手機號碼、自動生成身份證號碼,自動生成隨機字串,自動生成全國各地區號,設定變數、拆分欄位,獲取字串中的指定欄位等等。
2、封裝
封裝完成公共方法,會與後面的工廠結合起來使用
commKeyword.py的程式碼段
import json import jsonpath import datetime from common.getConfig import myCof from common.getCase import Getcase from common.operatorDB import OpeartorDB from common.parameteriZation import Paramete, analyzing_param, replace_data from common.sendApirequest import SendApirequests from common.sendEmail import SendMail from common.sendMsg import SendMsg class CommKeyword(object): def __init__(self): self.operatordb = OpeartorDB() self.getCase = Getcase() self.sendApi = SendApirequests() self.sendMail = SendMail() self.sedMsg = SendMsg() def get_exceut_case(self, **kwargs): """ 獲取當前執行用例 :return: bl, cases 一引數返回成功與否,二引數用例或失敗原因 """ try: cases = self.getCase.read_all_excels() except Exception as e: return False, '獲取用例失敗,原因:' + str(e) return True, cases def get_current_casefile_name(self, **kwargs): """ 獲取執行用例檔名稱 :return: 返回用例檔名稱 """ try: fileName = myCof.getValue('case', 'testcase') except Exception as e: return False, '引數中未設定用例檔名稱,請檢查配置檔案' return True, fileName def send_api(self, **kwargs): """ 傳送用例請求 post, get :param kwargs: 請求的引數 ,有url,headers,data等 :return: bl, cases 一引數返回成功與否,二引數請求結果或失敗原因 """ try: url = replace_data(kwargs['url']) method = kwargs['method'] if kwargs['headers'] is None: headers = None else: _isOk, result = self.format_headers(replace_data(kwargs['headers'])) if _isOk: headers = result else: return _isOk, result if kwargs['data'] is not None: try: jsondata = json.loads(replace_data(kwargs['data'])) data = None except ValueError: data = replace_data(kwargs['data']) jsondata = None else: data = None jsondata = None response = self.sendApi.request_Obj(method=method, url=url, json=jsondata, data=data, headers=headers) except Exception as e: return False, '傳送請求失敗' + str(e) return True, response def set_sheet_dict(self): """ :return: excl檔案裡面的sheet頁資訊 """ xlsx = Getcase(myCof.get('excel', 'casename')) sh_dict = xlsx.sheet_count() setattr(Paramete, 'sheetdict', sh_dict) sheetdict = getattr(Paramete, 'sheetdict') return sheetdict def set_common_param(self, key, value): """ :param key: 公共變數名 :param value: 引數 :return: """ setattr(Paramete, key, value) def get_commom_param(self, key): """ :param key: 公共變數名 :return: 取變數值 """ return getattr(Paramete, key) def get_current_sheet_name(self): """ :return: 返回當前執行用例的sheet頁名稱 """ sh_index = self.get_commom_param('sheetindex') sh_dict = self.get_commom_param('sheetdict') for sh in sh_dict: if sh.title().find(str(sh_index)) != -1: sheet_name = sh_dict[sh.title().lower()] return sheet_name def get_json_value_as_key(self, *args, **kwargs): """ 得到json中key對應的value,存變數param 預設傳的引數為: result:用來接收結果的變數 method:呼叫的方法 ,帶不帶${ } 都行 param_x:引數,數量不限。格式可為${ }會替換為已存在的資料 """ try: param = kwargs['result'] jsonstr = kwargs['param_1'] key = kwargs['param_2'] except KeyError: return False, '方法缺少引數,執行失敗' param = analyzing_param(param) jsonstr = replace_data(jsonstr) key = replace_data(key) if param is None or jsonstr is None or key is None: return False, '傳入的引數為空,執行失敗' try: result = json.loads(jsonstr) except Exception: return False, '傳入字典引數格式錯誤,執行失敗' key = '$..' + key try: value = str(jsonpath.jsonpath(result, key)[0]) except Exception: return False, '字典中[' + jsonstr + ']沒有鍵[' + key + '], 執行失敗' setattr(Paramete, param, value) return True, ' 已經取得[' + value + ']==>[${' + param + '}]' def format_headers(self, param): """ 格式化請求頭 :param param:excel裡讀出出來的header,是從瀏覽器f12裡直接copy的 :return: """ if param is None: return False, 'Headers為空' list_header = param.split('\n') headers = {} for li in list_header: buff = li.split(':') try: headers[buff[0]] = buff[1] except IndexError: return False, 'Headers格式不對' return True, headers def set_variable(self, **kwargs): """ 設定變數 :param kwargs: :return: """ try: var = kwargs['result'] param = kwargs['param_1'] except KeyError: return False, '方法缺少引數,執行失敗' if var is None or param is None: return False, '傳入的引數為空,執行失敗' setattr(Paramete, var, param) return True, ' 已經設定變數[' + param + ']==>[${' + var + '}]' def execut_sql(self, **kwargs): """ 執行SQL :param kwargs: :return: """ try: sql = kwargs['param_1'] except KeyError: return False, '方法缺少引數,執行失敗' try: var = kwargs['result'] par = kwargs['param_2'] except Exception: var = None isOK, result = self.operatordb.excetSql(sql) if isOK and var is not None: data = result[par] setattr(Paramete, var, data) return True, '執行SQL:[' + sql + ']成功,取得' + par + '的資料[' + data + ']==>[${' + var + '}]' elif isOK and var is None: return True, '執行SQL:[' + sql + ']成功' elif isOK is False: return isOK, result def send_email(self): """ 傳送郵件 :return: """ return self.sendMail.send_email() def send_msg(self, **kwargs): """ 傳送訊息 :param kwargs: :return: """ title = kwargs['title'] url = kwargs['url'] code = kwargs['code'] result = kwargs['result'].encode('utf-8').decode('unicode_escape') nowTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 現在 msg = nowTime + '\n用例名稱:' + title + '\n請求:' + url + '\n響應碼:' + code + '\n響應資訊:' + result self.sedMsg.sendMsg(msg) # header = 'Access-Control-Allow-Credentials: true\nAccess-Control-Allow-Origin: http://test-hcz-static.pingan.com.cn\naccessToken: 8b1f056249134c4f9fb7b573b25ce08c' # _isOK, headers = format_headers(header) # print(headers, type(headers))
十一、工廠封裝(核心)-methodFactoy.py
1、工廠的邏輯
工廠是公共方法呼叫入口。
有人傳入關鍵的文字,而工廠會找到關鍵文字對應的公共方法,執行方法得到結果,最後返回給呼叫工廠的人。
那我們如何通過文字找方法呢?
需要用到配置檔案,我們在配置檔案將前面小節封裝的公共方法逐一配置好,
格式:文字=方法名稱
如下圖:
程式碼實現,先通過配置檔案得到公共方法的名稱 ,然後使用getattr方法在公共模組物件上找到公共方法,然後執行方法,可以得到想要的結果。
如下圖
如果是在用例裡執行,以這種格式寫
列印結果與報告展示
這個需要將後面的封閉與用例解析寫完才能看到的效果,加油
2、封裝
methodFactory.py的程式碼段
from common.getConfig import myCof from kemel.commKeyword import CommKeyword # #初始化公共方法模組 # comKey = CommKeyword() # #獲取所有公共方法配置引數 # comKW = dict(myCof.items('commkey')) # #獲取 取用例方法名稱 # method_Name = comKW['獲取當前執行用例'] # #通過getattr 獲取公共方法模組的對應的模組 # func = getattr(comKey, method_Name, None) # #執行前面獲取的公共方法得到用例 # cases = func(aa=None) # #列印用例 # print(cases) class MethodFactory(object): def __init__(self): self.comKey = CommKeyword() self.comKW = dict(myCof.items('commkey')) def method_factory(self, **kwargs): """ 用例公共方法工廠 預設傳的引數為: result:用來接收結果的變數,格式可為${abc} method:呼叫的方法,這裡設計方法都使用中文 param_x:引數,數量不限。格式可為${abc}會替換為已存在的資料 """ if kwargs.__len__() > 0: try: kwargs['method'] except KeyError: return False, 'keyword:用例[method]欄位方法沒引數為空.' try: method = self.comKW[str(kwargs['method']).lower()] except KeyError: return False, 'keyword:方法[' + kwargs['method'] + '] 不存在,或未配置.' else: return False, '沒有傳參' try: func = getattr(self.comKey, method, None) _isOk, reselt = func(**kwargs) return _isOk, reselt except Exception as e: return False, 'keyword:執行失敗,估計不存在,異常:' + str(e) # fac = MethodFactory() # print(fac.method_factory(method='獲取當前用例檔名稱'))
十二、解析用例(核心)-testCase.py
1、詳情講解
python 自動了一個單元測試框架unittest,用來做自動化測試是絕好的。
先引用一段理論:
--------------------------------------------
做過自動化測試的同學應該都知道python中的unittest框架,它是python自帶的一套測試框架,學習起來也相對較容易,unittest框架最核心的四個概念:
test case:就是我們的測試用例,unittest中提供了一個基本類TestCase,可以用來建立新的測試用例,一個TestCase的例項就是一個測試用例;unittest中測試用例方法都是以test開頭的,且執行順序會按照方法名的ASCII值排序。
test fixure:測試夾具,用於測試用例環境的搭建和銷燬。即用例測試前準備環境的搭建(SetUp前置條件),測試後環境的還原(TearDown後置條件),比如測試前需要登入獲取token等就是測試用例需要的環境,執行完後執行下一個用例前需要還原環境,以免影響下一條用例的測試結果。
test suite:測試套件,用來把需要一起執行的測試用例集中放到一塊執行,相當於一個籃子。我們可以使用TestLoader來載入測試用例到測試套件中。
test runner:用來執行測試用例的,並返回測試用例的執行結果。它還可以用圖形或者文字介面,把返回的測試結果更形象的展現出來,如:HTMLTestRunner。
--------------------------------------------
自動化測試流程:是基於unittest中TestCase + ddt data 模式成自動化用例集(俗稱資料驅動)。而後被unittest中的test suite套件將用例集中管理起來,最後使用unittest中的test runner將集中起來的用例執行,生成測試報告
解析用例。就是資料驅動這一段。已經封裝好在testCase.py中了,可以自行看程式碼與註釋學習
2、封裝
testCase.py程式碼段
import unittest import json from library.ddt import ddt, data from common.log import mylog from kemel.methodFactory import MethodFactory isOK = True e = Exception() @ddt #引用資料驅動裝飾器 class TestCase(unittest.TestCase): metFac = MethodFactory() #初始化工廠類 isOK, cases = metFac.method_factory(method='獲取當前執行用例') isOK, fileName = metFac.method_factory(method='獲取當前用例檔名稱') if isOK is False: mylog.error('獲取用例失敗') quit() #呼叫工廠公共方法入口 def _opear_keyword(self, **kwargs): return self.metFac.method_factory(**kwargs) #斷言方法 def _assert_res_expr(self, rules, reponse, expr): """ 斷言方法 :param rules:結果包含、結果等於、結果狀態 :param res: :param expr: :return: """ try: res = reponse.json() except Exception: res = reponse.text headers = reponse.headers code = str(reponse.status_code) _reason = 'success' if rules == '結果包含': if type(expr) is str: res = json.dumps(res, ensure_ascii=False) print_result = json.dumps(json.loads(res), sort_keys=True, indent=2, ensure_ascii=False) else: print_result = res try: self.assertIn(expr, res) except AssertionError as e: _isOk = False _reason = '結果:\n【' + print_result + '】\n 不包含校驗值:\n 【' + expr + '】' else: _isOk = True _reason = '結果:\n【' + print_result + '】\n 包含有校驗值:\n 【' + expr + '】' elif rules == '結果等於': if type(expr) is str: res = json.dumps(res, ensure_ascii=False) print_result = json.dumps(json.loads(res), sort_keys=True, indent=2, ensure_ascii=False) else: print_result = res try: self.assertEqual(expr, res) except AssertionError as e: _isOk = False _reason = '結果:\n【' + res + '】\n 不等於校驗值:\n 【' + expr + '】' else: _isOk = True _reason = '結果:\n【' + res + '】\n 等於校驗值:\n 【' + expr + '】' elif rules == '結果狀態': try: self.assertEqual(expr, code) except AssertionError as e: _isOk = False _reason = '結果:\n【' + code + '】\n 不等於校驗值:\n 【' + expr + '】' else: _isOk = True _reason = '結果:\n【' + code + '】\n 等於校驗值:\n 【' + expr + '】' elif rules == '頭部包含': if type(expr) is str: headers = json.dumps(headers, ensure_ascii=False) print_header = json.dumps(json.loads(headers), sort_keys=True, indent=2, ensure_ascii=False) else: print_header = headers try: self.assertIn(expr, headers) except AssertionError as e: _isOk = False _reason = '結果頭:\n【' + print_header + '】\n 不包含校驗值:\n 【' + expr + '】' else: _isOk = True _reason = '結果頭:\n【' + print_header + '】\n 包含有校驗值:\n 【' + expr + '】' elif rules == '頭部等於': if type(expr) is str: headers = json.dumps(headers, ensure_ascii=False) print_header = json.dumps(json.loads(headers), sort_keys=True, indent=2, ensure_ascii=False) else: print_header = headers try: self.assertEqual(expr, headers) except AssertionError as e: _isOk = False _reason = '結果頭:\n【' + print_header + '】\n 不等於校驗值:\n 【' + expr + '】' else: _isOk = True _reason = '結果頭:\n【' + print_header + '】\n 等於校驗值:\n 【' + expr + '】' return _isOk, _reason #列印用例資訊與執行結果,因為是TestCase,最終它們將展示到測試報告中,所以設計輸出格式,讓報告美美達 def postPinrt(self, **case): if case['interface'] is not None: print('\n------------------------------------------------------------------\n') print('介面:【' + case['interface'] + '】') if case['method'] is not None: print('型別:【' + case['method'] + '】') if case['data'] is not None: print('引數:【' + case['data'] + '】') if 'get' == str.lower(case['method']): if '?' in str(case['url']): url = str(case['url']) a, param = url.split('?') if param is not None: print('引數:【') datalist = str(param).split('&') for data in datalist: print(data) print('】') else: print('【沒帶引數】') print('\n------------------------------------------------------------------\n') @data(*cases) #,資料驅動裝飾器,將用例list中的元素出來,將元素傳遞給test_audit的case中 def test_audit(self, case): _isOk = True #如果interface為commfun,將呼叫對應的公共方法 if case['interface'] == 'commfun': """ 如果介面是公共方法,那麼欄位如下 method:公共方法名 title: 返回接果 url:引數 data:引數 ...暫時四個引數 """ _isOk, _strLog = self._opear_keyword(method=case['method'], result=case['title'], param_1=case['url'], param_2=case['headers'], param_3=case['data'], param_4=case['validaterules']) else: rows = case['case_id'] + 1 title = case['title'] expect = str(case['expected']) #傳送請求,用例檔案裡interface不等於commfun,method為post或get的將被執行 _isOK, result = self.metFac.method_factory(**case) if _isOk: response = result code = str(response.status_code) try: res = json.dumps(response.json()) self.metFac.method_factory(method='設定變數', result='response', param_1=response) #返回json存 except ValueError: res = response.text self.metFac.method_factory(method='設定變數', result='response', param_1=res) #返回html 或xml、 txt存 if case['validaterules'] is None: _isOk = True _strLog = '用例[' + str(case['case_id']) + ']:[' + title + ']執行完成.' else: rules = case['validaterules'] _isOk, _reason = self._assert_res_expr(rules, response, expect) if _isOk: _strLog = '用例[' + str(case['case_id']) + ']:[' + title + ']執行通過. \n 校驗結果:\n' + _reason else: _strLog = "用例[" + str(case['case_id']) + ']:[' + title + ']執行不通過.\n 原因:\n' + _reason #報錯的介面,給企業微信傳送資訊 self.metFac.method_factory(title=title, method='傳送訊息', api=case['interface'], url=case['url'], code=code, result=res) else: _strLog = "用例[" + str(case['case_id']) + ']:[' + title + ']執行不通過. \n 原因:\ n' + result if _isOk: mylog.info(_strLog) print(_strLog) self.postPinrt(**case) else: mylog.error(_strLog) print(_strLog) self.postPinrt(**case) raise
十三、最後的執行檔案-testRun.py
1、程式碼封裝
testRun.py程式碼段
寫完這個檔案的程式碼,再然後按照第三小節的 ’讀取用例模組-getCase.py‘ 裡面的規則設計測試用例,然後放支data檔案目錄裡,將配置檔案裡的用例檔名稱配置好,然後執行自動化測試了
import unittest import os from common.initPath import CASEDIR, REPORTDIR from kemel.methodFactory import MethodFactory from library.HTMLTestRunnerNew import HTMLTestRunner class TestRun(object): metFac = MethodFactory() def __init__(self): self.suit = unittest.TestSuite() load = unittest.TestLoader() self.suit.addTest(load.discover(CASEDIR)) self.runner = HTMLTestRunner( stream=open(os.path.join(REPORTDIR, 'report.html'), 'wb'), title='介面自動化測試報告', description='代替手動冒煙、手動迴歸,做更精準的測試', tester='HunWei' ) def excute(self): self.runner.run(self.suit) self.metFac.method_factory(method='傳送郵件') if __name__=='__main__': run = TestRun() run.excute() # from kemel.methodFactory import MethodFactory # abc = MethodFactory() # isOK, cases = abc.method_factory(method='獲取當前執行用例') # print(cases)
執行後的測試報告如下: