目錄
1、前言
2、思路
3、正文
一、路徑模塊-initPath.py
二、組態檔模塊-getConfig.py
三、讀取用例模塊-getCase.py
四、資料庫操作模塊-operatorDB.py
五、日志模塊-log.py
六、郵件模塊-sendEmail.py
七、訊息模塊-sendMsg.py
八、變數引數化模塊-parameteriZation.py
九、API請求模塊-sendApirequests.py
十、公共方法的模塊(核心)-commKeyword.py
十一、工廠封裝(核心)-methodFactoy.py
十二、決議用例(核心)-testCase.py
十三、最后的運行檔案-testRun.py
1、前言
自動化測驗,是測驗道路上不可或缺的重要部分,現在有很多自動測驗工具,基本可以滿足軟體市場的測驗要求,但使用工具讓人知其然而不知其所以然,學會了也只是一個自動化測驗工具人,所以學會自動化框架,是擺脫工具人、提升自己、加薪升職的必經之路;
天王劉德華說:學到了要教人,
這是一個已經學會了的人分享的一點知識,希望測驗同胞們在前進路上路過我這篇博客時,如得感覺有一點點的幫助,請留下您的一個贊,
觀看此文時,需要有入門級的Python代碼基礎,如沒有,請先去找視頻教程學習一段時間,
本文所有講解代碼與執行結果用截圖展示,這為了是讓您看著可以有個寫代碼的程序,提升自己;當然如果不想寫,也可以只接跳轉每一小節末尾,整體代碼展示,可以"使用程式員高階技能ctrl+c, ctrl+v"自行學習,
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:https://download.csdn.net/download/weixin_40331132/14022116
HTMLRunner: https://download.csdn.net/download/weixin_40331132/14022157
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)
執行后的測驗報告如下:


總結:
做測驗的,語言組織能力不太在行,希望大家能看的懂,等哪天開心,會公開原始碼,希望諸位給個小贊,謝謝!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/244675.html
標籤:python
