前程貸是一個網貸信息服務平臺,p2p模式,主要業務流程:借款人發佈借款項目,經管理員審覈,進入競標狀態後,投資人選擇可投資項目進行投資。用戶能夠同時爲借款人和投資人。python
用戶模塊:註冊、登陸、充值、提現、更新暱稱、投資、用戶信息,共7個接口mysql
項目模塊:新增項目、審覈項目、分頁獲取項目列表,共3個接口正則表達式
實現5個接口的自動化測試:註冊、登陸、充值、新增項目、投資sql
數據流記錄,5張表數據庫
會員表member,保存平臺會員數據。用戶名、密碼(加密)、手機號、用戶類型、可用餘額、註冊時間json
項目表loan,保存平臺項目數據。借款人id,標題,借款金額,年利率,借款期限、借款期限類型、競標天數、建立時間、競標開始時間、結束時間、項目狀態cookie
投資表invest,保存平臺投資記錄數據,用戶投資後就會在表.;裏新增一條投資數據。投資人id、標id、投資金額、建立時間、是否有效55網絡
回款計劃表repayment,滿標後,每份投資會生成一條或多條回款計劃記錄。session
平臺會員資金流水記錄表,只要會員可用餘額有變更,就會在這個表新增一條記錄。app
接口地址、請求方法、請求頭、請求參數
響應體、接口鑑權等
小結:需求分析中,理解業務邏輯,以及數據流在數據庫中的映射關係,明確每一個接口的信息,包括地址、請求方法、類型、請求參數等
excel編寫,一個表單表明一個測試模塊,用例內容包括:用例編號、名稱、接口地址、請求方法、請求參數(json格式)、預期結果、實際結果、是否經過
測試用例的設計方法:等價類劃分、邊界值分析、錯誤推測法(全角字符串、超長混合字符串、數字0、單引號)
總共寫了100多條測試用例。
使用postman對程序的主要功能進行驗證。
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執行套件
請求處理、excel用例讀取、配置信息的處理、日誌記錄處理、參數化&正則表達式、數據校驗pymysql、接口依賴處理、unittest單元測試框架、ddt數據驅動、Jenkins單元持續集成等
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()
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文件修改後,必定要保存
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