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