轉自: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)
執行后的測試報告如下: