主頁 > 後端開發 > Python從無到有搭建介面(API)自動化測驗框架

Python從無到有搭建介面(API)自動化測驗框架

2021-01-05 10:11:19 後端開發

目錄

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

上一篇:Django學習之框架搭建(詳細)

下一篇:各類炫酷二維碼的制作(附帶GUI工具開發)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more