介面實戰專案總結上
目錄
- 1. 前程貸業務分析
- 2. 測試用例編寫
- 3. 冒煙測試
- 4.分層設計理念+資料驅動思想搭建測試框架
- 5. 介面自動化測試框架的技術點
- 6.封裝—requests介面請求
- 7. 封裝—excel資料讀寫
- 8. 封裝—資料庫處理
1. 前程貸業務分析
1.1 平臺介紹
前程貸是一個網貸資訊服務平臺,p2p模式,主要業務流程:借款人釋出借款專案,經管理員稽核,進入競標狀態後,投資人選擇可投資專案進行投資。使用者可以同時為借款人和投資人。
使用者模組:註冊、登入、充值、提現、更新暱稱、投資、使用者資訊,共7個介面
專案模組:新增專案、稽核專案、分頁獲取專案列表,共3個介面
實現5個介面的自動化測試:註冊、登入、充值、新增專案、投資
1.2 資料庫
資料流記錄,5張表
會員表member,儲存平臺會員資料。使用者名稱、密碼(加密)、手機號、使用者型別、可用餘額、註冊時間
專案表loan,儲存平臺專案資料。借款人id,標題,借款金額,年利率,借款期限、借款期限型別、競標天數、建立時間、競標開始時間、結束時間、專案狀態
投資表invest,儲存平臺投資記錄資料,使用者投資後就會在表.;裡新增一條投資資料。投資人id、標id、投資金額、建立時間、是否有效55
回款計劃表repayment,滿標後,每份投資會生成一條或多條回款計劃記錄。
平臺會員資金流水記錄表,只要會員可用餘額有變動,就會在這個表新增一條記錄。
1.3 介面資訊
-
介面地址、請求方法、請求頭、請求引數
-
響應體、介面鑑權等
小結:需求分析中,理解業務邏輯,以及資料流在資料庫中的對映關係,明確每個介面的資訊,包括地址、請求方法、型別、請求引數等
2. 測試用例編寫
excel編寫,一個表單代表一個測試模組,用例內容包括:用例編號、名稱、介面地址、請求方法、請求引數(json格式)、預期結果、實際結果、是否通過
測試用例的設計方法:等價類劃分、邊界值分析、錯誤推測法(全形字串、超長混合字串、數字0、單引號)
總共寫了100多條測試用例。
3. 冒煙測試
使用postman對程式的主要功能進行驗證。
4.分層設計理念+資料驅動思想搭建測試框架
- 結構清晰:測試用例層(業務層)、配置檔案層、資料層、日誌層、報告層、指令碼層
- 減少程式碼冗餘:資料驅動思想,測試資料與用例執行邏輯分離,一個測試用例指令碼可以對資料進行批量處理
python + unittest + ddt + requests
import unittest
@ddt.ddt # 裝飾器,該類範圍內會自動建立多個例項方法
class TestRegister(unittest.TestCase): # 新建一個測試類,並繼承unittest.TestCase父類
@classmethod
def setUpClass(cls): # 初始化所有用例的公共操作,建立請求物件,構造請求引數等
pass
@classmethod
def tearDownClass(cls): # 用於所有用例的公共資源釋放,例如關閉介面請求會話物件
pass
@ddt.data(*testdatas) # 對序列型別拆包,引數傳遞
def test_register(self, testcase): # 測試用例:訪問介面、傳參、獲取響應值、斷言操作
pass
if __name__ == '__main__':
unittest.main() # 依據ACSICC值的順序執行
# 調整執行順序TestSuit套件,呼叫addTest方法,TextTestRunner執行套件
5. 介面自動化測試框架的技術點
請求處理、excel用例讀取、配置資訊的處理、日誌記錄處理、引數化&正則表示式、資料校驗pymysql、介面依賴處理、unittest單元測試框架、ddt資料驅動、Jenkins單元持續整合等
6.封裝—requests介面請求
import json
import requests
class HttpRequest:
def __init__(self):
# 建立會話物件,自動化維護cookie資訊
self.session = requests.Session()
# 發起請求
def send(self, method, url, **kwargs): # 關鍵字引數包括headers、json、cookies等
method = method.upper() # 請求方法大寫
kwargs["json"] = self.handle_param("json", kwargs)
kwargs["data"] = self.handle_param("data", kwargs)
return self.session.request(method, url, **kwargs)
# 請求引數處理
@staticmethod
def handle_param(param_name, param_dict):
# 不管輸入的是json格式的字串,還是字典字串,或者是字典,都能轉為字典輸出
if param_name in param_dict:
data = param_dict.get(param_name)
if isinstance(data, str):
try:
data = json.loads(data) # 將json字串(python中格式為‘{}’)轉換成字典
except Exception:
data = eval(data) # 直接將字串最外層的引號拿掉,字典形式
return data
# 新增請求頭,公共請求頭更新
def add_headers(self, one_dict): # 請求頭引數,字典型別
self.session.headers.update(one_dict)
# 關閉會話,釋放資源
def close(self):
self.session.close()
7. 封裝—excel資料讀寫
import os
from openpyxl import load_workbook
class Testcase: # 通過建立不同的物件儲存每一條測試用例,用例資料通過建立例項屬性來儲存,具有全域性通用的作用
pass
class HandleExcel:
def __init__(self, filename, sheetname=None):
self.filename = os.path.join(DATA_PATH, filename)
self.sheetname = sheetname
def read_data(self):
wb = load_workbook(self.filename) # 載入excel檔案
if self.sheetname == None:
ws = wb.active # 預設讀取第一個表單
else:
ws = wb[self.sheetname] # 獲取指定表單物件
testcases_list = [] # 存放資料
headers_list = [] # 存放表頭資訊
for row in range(1, ws.max_row + 1):
one_testcase = Testcase() # 建立物件,通過動態建立例項屬性的方法存放每一行用例
for column in range(1, ws.max_column + 1):
one_cell = ws.cell(row, column) # 建立單元格物件
one_cell_value = one_cell.value
if row == 1:
headers_list.append(one_cell_value)
else:
key = headers_list[column - 1]
setattr(one_testcase, str(key), one_cell_value) # 設定當前用例所對應的表頭屬性
if key == "actual":
setattr(one_testcase, "actual_column", column) # 設定存放實際響應報文所在列的列號屬性
elif key == "result":
setattr(one_testcase, "result_column", column) # 設定存放用例執行結果所在列的列號屬性
if row != 1:
setattr(one_testcase, "row", row) # 設定當前用例所在的行號屬性
testcases_list.append(one_testcase)
return testcases_list # 列表的元素是物件
def write_data(self, one_testcase, actual_value, result_value):
wb = load_workbook(self.filename) # 載入指定excel檔案
if self.sheetname == None:
ws = wb.active
else:
ws = wb[self.sheetname] # 訪問表單
ws.cell(one_testcase.row, one_testcase.actual_column, value=actual_value) # 訪問指定單元格並寫入資料
ws.cell(one_testcase.row, one_testcase.result_column, value=result_value) # 寫入狀態時,一定要將excel檔案關閉
wb.save(self.filename) # 對excel檔案修改後,一定要儲存
8. 封裝—資料庫處理
import random
import pymysql
from scripts.handle_yaml import do_yaml
class HandleMysql:
def __init__(self):
# 1.建立連線物件
self.conn = pymysql.connect(host=do_yaml.get_data('mysql', 'host'),
user=do_yaml.get_data('mysql', 'user'),
password=do_yaml.get_data('mysql', 'password'),
port=do_yaml.get_data('mysql', 'port'),
database=do_yaml.get_data('mysql', 'database'),
charset="utf8", # 注意這裡不能寫成utf-8
cursorclass=pymysql.cursors.DictCursor)
self.cursor = self.conn.cursor() # 2.建立遊標物件
# 3.獲取一條資料,字典型別
def get_one_value(self, sql, args=None):
self.cursor.execute(sql, args=args)
self.conn.commit()
return self.cursor.fetchone()
# 4.獲取多條資料,巢狀字典的列表型別
def get_values(self, sql, args=None):
self.cursor.execute(sql, args=args)
self.conn.commit()
return self.cursor.fetchall()
# 5.關閉遊標,再關閉連線
def close(self):
self.cursor.close()
self.conn.close()
@staticmethod
def generate_telephone():
"""
隨機生成手機號
手機號規則:前3位—網路識別號;第4-7位—地區編碼;第8-11位—使用者號碼
第1位:1;
第2位:3,4,5,7,8
第3位:3:【0,9】, 4:【5,7】, 5:【0,9】, 7:【6,7,8】, 8:【0-9】
:return:返回一個手機號碼
"""
# 前三位
second = random.choice([3, 4, 5, 7, 8])
third = str({
3: random.randint(0, 9),
4: random.choice([5, 7]),
5: random.randint(0, 9),
7: random.choice([6, 7, 8]),
8: random.randint(0, 9)
}[second])
# 後八位
eight = ''.join(random.sample('0123456789', 8))
return '1' + str(second) + third + eight
# 在資料庫中查詢隨機生成的手機號是否存在
def check_telephone(self, telephone):
sql = do_yaml.get_data('mysql', 'select_user_sql')
if self.get_one_value(sql, args=[telephone]):
return True
else:
return False
# 得到一個在資料庫中不存在的手機號
def get_new_telephone(self):
while True:
one_mobile = self.generate_telephone()
if not self.check_telephone(one_mobile):
break
return one_mobile
def get_not_existed_user_id(self):
# 從yaml配置檔案中獲取查詢最大使用者id的sql語句
sql = do_yaml.get_data('mysql', 'select_max_user_id_sql')
# # 獲取最大的使用者id + 1
not_existed_id = self.get_one_value(sql).get('max(id)') + 1
return not_existed_id
def get_not_existed_loan_id(self):
sql = do_yaml.get_data('mysql', 'select_max_loan_id_sql')
# # 獲取最大的使用者id + 1
not_existed_id = self.get_one_value(sql).get('max(id)') + 1
return not_existed_id