? ? ? ? ? ? 本問主要介紹用excel表格來管理接口用例,采用python+unittest測試框架踩娘,結合ddt數據驅動橘沥,最后結合BeautifulReport報告插件毅戈,生成最終的測試報告
首先,來3張圖检痰,了解輸入數據包归,輸出結果
?????1、需要測試的接口case:execel表格管理
? ??2铅歼、請求的body:request_data.py文件中字典req_data公壤,用來存放所有case請求的body
? ? 3、利用unittest+ddt+BeautifulReport生成HTML測試報告:
其次椎椰,附上整個項目的結構圖
最后厦幅,分解項目運行的細節(jié)內容
1、項目的主運行文件:run_ddt.py
(1)慨飘、導入我們run_ddt.py文件運行所需要的第三方包
# coding=utf-8
import unittest
import time
import os
from BeautifulReport import BeautifulReport
(2)确憨、生成我們需要的report路徑
curpath = os.path.dirname(os.path.realpath(__file__))
reprot_path = os.path.join(curpath, "report")
(3)、匹配該項目里瓤的,以test開頭的文件休弃,并添加成一個unittest測試集
def add_case(casepath=curpath, rule="test_*.py"):
? ? discover = unittest.defaultTestLoader.discover(casepath, pattern=rule)
? ? return discover
(4)、得到了測試集圈膏,便可以運行整個測試集里面的測試用例
def run_case(all_case, reportpath=reprot_path):
? ? now = time.strftime("%Y%m%d%H%M%S")
? ? print('測試報告生成地址: %s' % reportpath)
? ? BeautifulReport(all_case).report(description='用例執(zhí)行情況', filename='測試報告_' + str(now), report_dir=reportpath)
# 該文件的main函數入口:
if __name__ == '__main__':
? ? cases = add_case()
? ? run_case(cases)
? ? ? ? 寫到上面第3步的時候塔猾,你就會聯想到,我們后續(xù)肯定會編寫一個test_開頭的py文件稽坤,而里面就是我們的測試內容丈甸。
? ? ? ? 是的,我們第2個文件尿褪,就是編寫我們的測試代碼睦擂,也可以說是我們的測試思路或者是測試的步驟。
2茫多、測試思路:test_case_all.py
(1)祈匙、導入該文件所運行的包忽刽,以及從公用模塊導入公用函數
from myrequests import MyRequests
from common.operationExcel import OperationExcel
from common.operationReqData import OperationReqDate
from common.dependentData import DependentData
from ddt import ddt, data, unpack
from common.operationSQL import connectSQL
from config import *
import json
import unittest
(2)天揖、我們是從一個excel的sheet表拿的數據夺欲,要想把這些數據利用ddt來驅動,就需要把整個excel表的數據全部拿出來今膊,然后再利用ddt來分割數據些阅,在用切割的數據進行單個case測試
class GetReqData(object):
# 初始化excel操作模塊類,才能調用該類下的函數方法
? ? def __init__(self):
? ? ? ? self.operation_excel = OperationExcel()
#? 利用excel類里面的方法斑唬,獲取excel表格的所有數據
? ? def get_data(self):
? ? ? ? exe_data_all = []? ? # 定義一個空的列表市埋,存放excel表格的數據
? ? ? ? rows_count = self.operation_excel.get_all_lines()? ? # 獲取表格中有多少行數據
? ? ? ? for i in range(1, rows_count):? ? # 循環(huán)遍歷取excel表格的數據,去除第一行
? ? ? ? ? ? exe_data = self.operation_excel.get_a_row_data(i)? ? # 把每一行的數據都取出
? ? ? ? ? ? exe_data_all.append(tuple(exe_data))? ? # 取出的數據恕刘,都添加到定義空列表中
? ? ? ? return exe_data_all? ? # 取值完成后缤谎,把所有的數據返回出去
# 單獨的把獲取數據函數進行調用一次,這樣ddt數據驅動褐着,才有數據作為參數傳入
get_req_data = GetReqData()
req_data = get_req_data.get_data()
(3)坷澡、ddt來驅動excel表的數據,獲取到的excel數據是一個list類型含蓉,提取每一行的數據就顯示輕松多了频敛。提取完數據,就可以進行request請求測試了馅扣。
# 采用ddt數據驅動斟赚,在運行的類前,就需要先運行ddt的裝飾器函數差油,故需要在Run類前加上@ddt
@ddt
class Run(unittest.TestCase):
? ? #? 集成unittest.TestCase方法拗军,然而需要初始化,在unittest里__init__函數無法使用厌殉,所以我們就用到unittest里的setUp食绿、?tearDown這樣函數來做類函數的初始化,這里初始化只需要運行一次公罕,這里我采用了setUpClass這個函數來實現
? ? @classmethod
? ? def setUpClass(cls):
? ? ? ? print('------執(zhí)行開始------')
? ? ? ? cls.operation_excel = OperationExcel()
? ? ? ? cls.operation_req_data = OperationReqDate()
? ? ? ? cls.dependent_data = DependentData()
? ? ? ? cls.m = MyRequests()
? ? ? ? cls.host = HOST? ? # 從config文件獲取host器紧,這樣切換地址不用改excel表的url內容
? ? ? ? cls.new_data_dict = {}
? ? @classmethod
? ? def tearDownClass(cls):
? ? ? ? print('------執(zhí)行完畢------')
#? ? ?初始化工作已完成,那就進入我們重點楼眷、重點铲汪、重點了
? ? @data(*req_data)? ? # ddt下data可以把數據進行切分返回數據,具體可參照ddt使用
? ? @unpack? ? # ddt下的一個方法罐柳,目的是把每一行數據分開傳參掌腰,具體使用ddt詳解
? ? def test_case(cls,? *exe_data):
? ? ? ??# 這里是判斷需要執(zhí)行SQL語句
? ? ? ? if exe_data[3] == "SQL":? ? ? ?
? ? ? ? ? ? sql = exe_data[5]
? ? ? ? ? ? connectSQL(exe_data[7], sql, cls.new_data_dict)
'''
這里判斷case是都需要執(zhí)行(運行的流程重點就在此)
我們從每一行數據取出來是一個list,根據list的下標张吉,獲取excel表格的值齿梁;
? ? 1、取決于該case是否運行,如果運行勺择,就往下取值创南,反之,則不用管省核;
? ? 2稿辙、獲取該case請求的body值,根據excel的req_data字段气忠,取對應的值
? ? 3邻储、如果該case有依賴,就需要走依賴函數旧噪,進行鍵位值的替換吨娜,實現實時數據變動;
? ? 4淘钟、進行接口的請求(如果沒有依賴萌壳,則可以跳過第3步)
? ? 5、進行預期結果與實際結果的對比
? ? 6日月、最后袱瓮,如果該case需要提取某個字段的值,根據鍵位爱咬,在返回的內容中進行提取
? ? 注意:new_data_dict這個字典尺借,是存放替換的值,格式是key=value精拟,key是我們自定義的名稱燎斩,value則是從返回值提取的值,提取數據必須在替換數據之前就有值蜂绎,不然會報錯栅表,因為提取的數據沒有值,替換的時候就無法找到值進行替換师枣。提取值是用了jsonpath的方法提取怪瓶,替換則是采用了自己定義的,以"."的方式代替層級關系践美。
'''
????elif exe_data[3] == 'YES':? ? ? ?# 第1步洗贰,判斷是否運行
? ? ? ? ? ? req_data = cls.operation_req_data.get_req_data(exe_data[5])? ? # 第2步取body
? ? ? ? ? ? print('執(zhí)行的用例ID: ', exe_data[0])
? ? ? ? ? ? data = json.loads(exe_data[9])? ? # 數據轉換,怕數據格式錯誤陨倡。
? ? ? ? ? ? if exe_data[7] != '':? ? # 判斷請求的body是否有依賴敛滋,此處判斷值為有依賴
? ? ? ? ? ? ? ? req_data = cls.dependent_data.replace_req_data(exe_data[7], req_data, cls.new_data_dict)? ? # 第3步,有依賴兴革,從提取值獲取進行替換(注:提取值必須有值)
? ? ? ? ? ? ? ? res = cls.m.myrequests(cls.host + exe_data[2], req_data, exe_data[4], exe_data[6])? ????? # 第4步绎晃,進行數據請求
? ? ? ? ? ? ? ? for key, value in data.items():? ? ? ? # 第5步蜜唾,預期結果與實際結果的對比
? ? ? ? ? ? ? ? ? ? res_value = cls.dependent_data.replace_data(key, res)
? ? ? ? ? ? ? ? ? ? cls.assertEqual(value, res_value)
? ? ? ? ? ? ? ? if exe_data[8] != '':? ? ? ? # 判斷是否需要提取
? ? ? ? ? ? ? ? ? ? cls.new_data_dict = cls.dependent_data.dependent_data(exe_data[8], res, cls.new_data_dict)? ? ? ? # 第6步,根據鍵位庶艾,在返回的內容提取值
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? res = cls.m.myrequests(cls.host + exe_data[2], req_data, exe_data[4], exe_data[6])? ? ????# 第4步灵妨,進行無body替換的接口請求
? ? ? ? ? ? ? ? for key, value in data.items():? ? # 第5步,預期結果與實際結果對比
? ? ? ? ? ? ? ? ? ? res_value = cls.dependent_data.replace_data(key, res)
? ? ? ? ? ? ? ? ? ? cls.assertEqual(value, res_value)
? ? ? ? ? ? ? ? if exe_data[8] != '':? ? #? 判斷是否需要提取
? ? ? ? ? ? ? ? ? ? cls.new_data_dict = cls.dependent_data.dependent_data(exe_data[8], res, cls.new_data_dict)? ? # 第6步落竹,根據提取的鍵位,在返回值中提取對應的值
# 該文件的程序入口
if __name__ == '__main__':
?????unittest.main()
3货抄、整個項目的脊柱已經弄好述召,現在就需要各個內容來支配整個項目
?----從test文件整理出,我們可以察覺到缺少的函數文件蟹地,我們一一列出:
---1积暖。excel表格的數據獲取方法
---2。請求body的數據獲取方法
---3怪与。提取值的方法
---4夺刑。替換body的方法
---5。接口請求的方法
從這5點中分别,我們就來一一編寫需要的方法:
3-1遍愿、excel的獲取數據方法:operationExcel.py
????在test文件里,我們發(fā)現了這兩句代碼耘斩,屬于excel的操作
rows_count = self.operation_excel.get_all_lines()
exe_data = self.operation_excel.get_a_row_data(i)
????那么我們就需要在common公用文件下新建一個operationExcel.py文件沼填,來針對excel表格數據的操作
# coding=utf-8
import xlrd, os, time, xlwt
from xlutils.copy import copy
class OperationExcel(object):
? ? def __init__(self, file_name=None, sheet_id=None):
? ? ? ? if file_name:
? ? ? ? ? ? self.file_name = file_name
? ? ? ? ? ? self.sheet_id = sheet_id
? ? ? ? else:
? ? ? ? ? ? self.file_name = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data/ApiList1.xlsx')
? ? ? ? self.data = self.get_data()
? ? # 獲取數據
? ? def get_data(self):
? ? ? ? data = xlrd.open_workbook(self.file_name)
? ? ? ? tables = data.sheets()[self.sheet_id]
? ? ? ? return tables
? ? # 獲取sheet下的行數
? ? def get_all_lines(self):
? ? ? ? tables = self.data
? ? ? ? return tables.nrows
? ? # 獲取某一行的內容
? ? def get_a_row_data(self, row_num):
? ? ? ? tables = self.data
? ? ? ? row_data = tables.row_values(row_num)
? ? ? ? return row_data
3-2、body的獲取數據方法:operationReqData.py
? ? 在test文件里括授,我們會發(fā)現以下的代碼:
req_data = cls.operation_req_data.get_req_data(exe_data[5])
? ? 這樣的代碼坞笙,是我們從excel表取標識字段,到request_data文件里req_data取對應key的value荚虚,這樣body就能取出來了
from data import request_data
class OperationReqDate(object):
? ? def __init__(self):
? ? ? ? self.data = request_data.req_data? # 修改req_data的文件名
? ? # 根據關鍵key來獲取req_data文件的內容
? ? def get_req_data(self, key):
? ? ? ? if key == '':
? ? ? ? ? ? return None
? ? ? ? return self.data.get(key)
3-3薛夜、根據excel的數據,提取返回值的內容:dependentData.py
? ? ????我們在現實的測試中版述,往往發(fā)現梯澜,這個接口運行的時候,會調用上一個接口的數據渴析,而且還有一些數據值腊徙,都是重復調用,總不可能請求一個接口檬某,去多次調用其他接口吧撬腾,這樣就導致了接口的請求量變大了,增加了服務器的負載能力恢恼。
? ? ? ? 解決方案:我們在請求前民傻,我們新建一個空的字典,自定義key來獲取對應的value值,成成一個新的字典漓踢,請求body需要的時候牵署,就直接從這里取值,這樣就減少了請求次數喧半。
? ? ? ? 然而奴迅,在test文件中,我們會發(fā)現有這樣的代碼存在:
if exe_data[8] != '':
? ? ? ? ? ? ? ? ? ? cls.new_data_dict = cls.dependent_data.dependent_data(exe_data[8], res, cls.new_data_dict)
? ? 這樣是進行判斷是否有提取值挺据,有則需要提取取具,反之則不需要,然后我們的提取方法:
? ? 提取方法的思路:
? ? ? ? 1扁耐、根據依賴的鍵位暇检,去遍歷返回的res,鍵位提取的格式:id=(result.id)
? ? ? ? 2婉称、找到了鍵位的值后块仆,把鍵位的key作為字典的key存放,找的值當做value存放王暗,組成一個新的字典
# 根據exe表格中的key=(value)來獲取一個新增的dict-data
? ? def dependent_data(self, dependent_data, res, data_dict):
? ? ? ? exe_data = dependent_data.split('\n')
? ? ? ? # print('11: ', exe_data)
? ? ? ? for data in exe_data:
? ? ? ? ? ? data_value = data.split('=')
? ? ? ? ? ? # print('data_value:', data_value)
? ? ? ? ? ? dependent_key = data_value[1]
? ? ? ? ? ? value = self.replace_data(dependent_key, res)
? ? ? ? ? ? data_dict[data_value[0]] = value
? ? ? ? return data_dict
在此時悔据,就會發(fā)現一個新的語句:
value = self.replace_data(dependent_key, res)
然而我們就需要在該文件下再創(chuàng)建一個函數方法,這里提取的方法是采用jsonpath:
def replace_data(self, data_key, data_value):
? ? ? ? """
? ? ? ? :param data_key: 依賴的key值
? ? ? ? :param data_value: 遍歷的返回頁面數據
? ? ? ? :return:
? ? ? ? """
? ? ? ? try:
? ? ? ? ? ? json_exe = parse(data_key)
? ? ? ? ? ? madles = json_exe.find(data_value)
? ? ? ? except Exception as msg:
? ? ? ? ? ? print(msg)
? ? ? ? return [madle.value for madle in madles][0]
????????這樣我們的程序就不會報錯俗壹,該方法的用途我也不做多解釋蜜暑,網上有類似的專業(yè)講解。那么我們繼續(xù)我們項目其他方法解析
3-4策肝、替換請求的body里的鍵位值:dependentData.py
? ? 在3-3中肛捍,我已經講解了提取值的方法,主要是為了便于替換的時候需要之众,在新的一個字典里拙毫,我們只有傳入key,就能把之前接口請求返回的value取到棺禾,進行替換缀蹄,就可以直接請求了,我在excel表的替換值的格式:id={{user_id}}膘婶,格式可以根據自己喜歡來寫缺前,切割點就需要重新變化下即可。
# 根據exe的表格key={{value}}去替換值
? ? def replace_req_data(self, dependent_data, req_data, new_data_dict):
? ? ? ? exe_data = dependent_data.split('\n')
? ? ? ? for data in exe_data:
? ? ? ? ? ? data_value = data.split('=')
? ? ? ? ? ? value = new_data_dict.get(data_value[1][2:-2])
? ? ? ? ? ? req_data = self.check_json_data.check_json_data(req_data, data_value[0], value)
? ? ? ? return req_data
? ? 在上面的方法中悬襟,發(fā)現有一行新的代碼
?req_data = self.check_json_data.check_json_data(req_data, data_value[0], value)
? ? 這行代碼是進行替換的操作衅码,遍歷操作替換的工作量大,因此我們重新編寫一個文件來實現此功能:
3-4-1脊岳、數據替換方法:checkJsonData.py
? ? 遍歷我們的請求的body逝段,根據對應的鍵位垛玻,去實現value的一個更新,實現數據更新功能
# coding=utf-8
from httprunner import exceptions, logger
from httprunner.compat import OrderedDict, basestring, is_py2
class Check_Json_Data(object):
? ? # 替換json數據中對應的value
? ? def change_json(self, json_content, query, new, delimiter='.'):
? ? ? ? raise_flag = False
? ? ? ? response_body = u"response body: {}\n".format(json_content)
? ? ? ? try:
? ? ? ? ? ? keys = query.split(delimiter)
? ? ? ? ? ? if len(keys) == 1:
? ? ? ? ? ? ? ? if isinstance(json_content, (list, basestring)):
? ? ? ? ? ? ? ? ? ? json_content[int(keys[0])] = new
? ? ? ? ? ? ? ? elif isinstance(json_content, dict):
? ? ? ? ? ? ? ? ? ? json_content[keys[0]] = new
? ? ? ? ? ? if len(keys) > 1:
? ? ? ? ? ? ? ? for key in keys:
? ? ? ? ? ? ? ? ? ? if isinstance(json_content, (list, basestring)):
? ? ? ? ? ? ? ? ? ? ? ? return self.change_json(json_content[int(key)], ".".join(keys[1:]), new, delimiter='.')
? ? ? ? ? ? ? ? ? ? elif isinstance(json_content, dict):
? ? ? ? ? ? ? ? ? ? ? ? return self.change_json(json_content[key], ".".join(keys[1:]), new, delimiter='.')
? ? ? ? except (KeyError, ValueError, IndexError):
? ? ? ? ? ? raise_flag = True
? ? ? ? if raise_flag:
? ? ? ? ? ? err_msg = u"Failed to extract! => {}\n".format(query)
? ? ? ? ? ? err_msg += response_body
? ? ? ? ? ? logger.log_error(err_msg)
? ? ? ? ? ? raise exceptions.ExtractFailure(err_msg)
# 數據替換的方法
? ? def check_json_data(self, old_req_data, dependent_key, values):
? ? ? ? """
? ? ? ? 把舊的請求數據奶躯,根據鍵位帚桩,替換掉舊數據
? ? ? ? :param old_req_data: json文件的舊數據
? ? ? ? :param dependent_key: excel表中的鍵位值
? ? ? ? :param values: 獲取依賴的接口返回的鍵位值,也就是新值
? ? ? ? :return: 返回一個替換后的請求數據
? ? ? ? """
? ? ? ? self.change_json(old_req_data, dependent_key, values)
? ? ? ? return old_req_data
3-5嘹黔、請求的方法:myrequests.py
? ? 我們采用request模塊進行url的請求账嚎,這里需要更新自己的token,各個平臺不同儡蔓,token的取值也不同郭蕉,這個因系統(tǒng)而異。
? ? 首先浙值,我們需要提取token
????# 獲取token
? ? def login(self):
? ? ? ? global token
? ? ? ? if "Authorization" in self.s.headers.keys():? ? ? ? # 判斷是否存在token,如果有就直接跳過
? ? ? ? ? ? # print('--------token is exits!!---------')
? ? ? ? ? ? return self.s
? ? ? ? else:
? ? ? ? ? ? excel_data = self.operation_excel.get_a_row_data(1)
? ? ? ? ? ? url = self.host + excel_data[2]
? ? ? ? ? ? req_data = login_data
? ? ? ? ? ? res = self.s.post(url, json=req_data)
? ? ? ? ? ? r = res.content.decode('utf-8')
? ? ? ? ? ? r = json.loads(r)
? ? ? ? ? ? token = r['result']['token']
? ? ? ? ? ? self.s.headers.update({"Authorization": token})
? ? ? ? ? ? print("----------token create successfully!--------")
? ? ? ? ? ? return self.s
????????其次檩小,封裝自己的請求方式开呐。網上有很多種封裝方式,小伙伴可以選擇自己喜歡的封裝方式规求,這里我貼上我自己的封裝方式筐付,方法不完美,能實現就好阻肿。
????# 自定義請求函數
? ? def myrequests(self, url, req_data, req_type, data_type):
? ? ? ? """
? ? ? ? 自定義請求函數
? ? ? ? :param url: 請求的url
? ? ? ? :param req_data: 請求的data
? ? ? ? :param req_type: 請求方式
? ? ? ? :param data_type: 數據的傳遞格式
? ? ? ? :return: res頁面結果
? ? ? ? """
? ? ? ? if req_type == "POST":? # 判斷請求方式:POST
? ? ? ? ? ? if data_type == 'JSON':? # 判斷請求參數的數據類型
? ? ? ? ? ? ? ? # post_data = json.loads(req_data)
? ? ? ? ? ? ? ? # res = self.login().post(url, json=req_data)
? ? ? ? ? ? ? ? res = self.login().post(url, json=req_data)
? ? ? ? ? ? elif data_type == '':
? ? ? ? ? ? ? ? res = self.login().post(url, data=req_data)
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? res = self.login().post(url, data=req_data)
? ? ? ? elif req_type == "GET":
? ? ? ? ? ? if data_type == 'JSON':
? ? ? ? ? ? ? ? get_params = json.loads(req_data)
? ? ? ? ? ? ? ? res = self.login().get(url, params=get_params)
? ? ? ? ? ? elif data_type == '':
? ? ? ? ? ? ? ? res = self.login().get(url, params=req_data)
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? res = self.login().get(url, params=req_data)
? ? ? ? elif req_type == "DELETE":
? ? ? ? ? ? if data_type == 'JSON':
? ? ? ? ? ? ? ? get_params = json.loads(req_data)
? ? ? ? ? ? ? ? res = self.login().delete(url, params=get_params)
? ? ? ? ? ? elif data_type == '':
? ? ? ? ? ? ? ? res = self.login().delete(url, params=req_data)
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? res = self.login().delete(url, params=req_data)
? ? ? ? print("request_req_url: ", res.url)
? ? ? ? print("request_req_data: ", req_data)
? ? ? ? res = res.content
? ? ? ? res = json.loads(res.decode('utf-8'))
? ? ? ? print('res: ', res)
? ? ? ? return res
在附上配置文件內容:config
# coding=utf-8
HOST = "http://172.16.62.66"
# HOST = "http://172.16.62.71"
SQL_IP = "172.16.62.66"
# SQL_IP = "172.16.62.71"
db_message = {
? ? ? ? "host": SQL_IP,
? ? ? ? "username": "root",
? ? ? ? "password": "123456",
? ? ? ? "port": 3306,
? ? ? ? "charset": "utf8"
}
login_url = HOST + '/xxx-x'x'x'x/login/login'
login_data = {
? ? ? ? "mobile": "18100000000",
? ? ? ? "smsCode": "888888"
}
????????總結:項目的方法封裝不是很好瓦戚,這里介紹我使用的辦法,如果有更好的方法丛塌,方便留言较解,多多研究,讓自動化測試更加完美赴邻。郵件的發(fā)送方法印衔,請求頭文件的更新,我這邊都沒做姥敛,后期實現了奸焙,再更新。彤敛。