跳轉到3.0版本https://www.cnblogs.com/df888/p/12031649.html
升級版本pyfacehttps://www.cnblogs.com/df888/p/12592716.html
最近在做公司項目的自動化接口測試,在現有幾個小框架的基礎上,反復研究和實踐,搭建了新的測試框架。利用業余時間,把框架總結了下來。
AIM框架介紹
AIM,是Automatic Interface Monitoring的簡稱,即自動化接口監測。是一種基於python unittest的自動化接口測試框架。
設計思想
框架根據python語言的特點,結合了面向對象和面向函數編程。
以高效編程為主要目的,避免為了封裝而封裝。輕配置,重編碼。
接口測試的主要處理對象是參數。如果完全進行數據與代碼的分離,就會造成變量,傳參的冗余,降低編程效率。
於是從不做數據與代碼分離出發,對於需要復用的參數,提取到類之外,視需要進行數據與代碼的分離。
做到有的放矢。兼顧效率和復用性,迭代分離,更具實用性。
目錄結構
case:測試用例
common:公共函數,全局變量
config:配置路徑等
data:數據文件
result:測試結果
util:工具類
run.py:用例執行入口
run_mail.py:執行后自動發送郵件入口
case
BaseCase
所有Case的基類。
封裝了requests庫的post和get函數req,用於發送請求。
調用assertEqual等方法,封裝了用例的斷言。比如檢查接口返回flag,檢查接口狀態200,檢查值相等。
項目Case
測試系統的用例。按模塊分別建立文件編寫腳本。
Env.py:環境配置,包括url處理,登錄對象login實例(用戶名、密碼),數據庫對象dao實例(數據庫連接)。
Public.py:公共模塊。存放本系統公共的變量、函數、用例等。
common
Func.py:公共函數,比如獲取時間日期,獲取隨機數,處理參數。
Login.py:登錄模塊,屬於各系統通用,故放於此目錄下。包括密碼加密,驗證碼處理,強制登錄。
Var.py:全局變量。比如token。
config
RelativePath.py:配置目錄、文件的相對路徑。
data
echarts數據存儲csv文件,項目接口清單等。
result
log:日志。logging實現。支持輸出到文件和打印控制台。文件暫時使用較少,主要打印控制台便於調試。
接口調用記錄:輸出每個測試方法調用接口的記錄,包括參數、響應、耗時等。
自動化測試報告:HTMLTestRunner.py實現的html頁面報告。
util
AutoCode.py:自動生成結構化測試代碼。
CSV.py:csv相關函數封裝。比如輸出接口調用記錄。
Excel.py:讀取和存儲excel文件。
Format.py:格式化。比如把瀏覽器復制的參數格式化為代碼中帶有縮進的json。
HTMLTestRunner.py:用於輸出自動化測試報告。
Log.py:封裝日志方法。
Mysql.py:數據庫相關操作。
Parewise.py:結對測試。一種測試技術,后文詳述。
Request.py:核心工具,封裝接口發送請求。
Mail.py:發送郵件。
run.py
執行測試用例入口,可以選擇執行一個或多個系統,也可以執行一個系統中一個或多個模塊。
核心模塊
BaseCase.req
通過requests封裝的發送接口請求的方法。
定義在BaseCase類的內部。
參數 | 說明 |
---|---|
p | 將url、headers、body、method統一封裝到一個json里面進行處理。 |
method='post' | 默認為post方法。接口以post居多。 |
jsondata='json' | 默認json參數。post方法的json或data,純json使用json參數即可。對於receive_json這種dict,采用data參數。 |
loglevel=3 | 默認為3。日志級別,輸出請求、響應信息到控制台或接口調用記錄.csv。 |
rtext=None | 一些get請求會返回html或pdf,在控制台或csv文件中影響顯示,可以指定文本進行替換。 |
發送請求,並計算耗時:
start = time.clock()
if method == 'post': # 關閉SSL認證
if jsondata == 'json':
r = self.timeoutTry("requests.post(p['url'], headers=p['headers'], json=p['body'], verify=False)", p)
elif jsondata == 'data':
r = self.timeoutTry("requests.post(p['url'], headers=p['headers'], data=p['body'], verify=False)", p)
else:
print('jsondata錯誤')
elif method == 'get':
r = self.timeoutTry("requests.get(p['url'], headers=p['headers'], params=p['body'])", p)
else:
print('method錯誤')
end = time.clock()
elapsed = decimal.Decimal("%.2f" % float(end - start))
其中的self.timeoutTry是為了處理響應超時,會在后續博文中介紹。
Parewise
結對測試。接口參數一般是多個,於是比較適合采用parewise進行用例設計。
parewaise的概念可以百度一下。
大概意思就是,大多數的bug都是條件的兩兩組合造成的,parewise就是針對兩兩組合的情況,設計測試用例。
算法為,如果某一組用例的組合結果,在其他組合中均出現,就刪除該組用例,從而精簡用例。
windows下有微軟的PICT,txt文件錄入參數后,命名行執行,就出來結果了。
比如參數
執行后結果,只有31條,精簡了很多。
這個基本上一秒就出來結果了。
我自己參考網上算法寫的,就要慢的多。
估計后面有時間了再看看能不能調優。
parewise算法:
cp = [] # 笛卡爾積
s = [] # 兩兩拆分
for x in eval('itertools.product' + str(tuple(param_list))):
cp.append(x)
s.append([i for i in itertools.combinations(x, 2)])
del_row = []
s2 = copy.deepcopy(s)
for i in range(len(s)): # 對每個進行匹配
t = 0
for j in range(len(s[i])): # 判斷所有同時都存在其他中 且位置相同
for i2 in [x for x in range(len(s2)) if s2[x] != s[i]]: # 其他 只比對有效
flag = False
for j2 in range(len(s2[i2])):
if s[i][j] == s2[i2][j2] and j == j2:
t = t + 1
flag = True
break
if flag:
break
if t == len(s[i]):
del_row.append(i)
s2.remove(s[i])
return [cp[i] for i in range(len(cp)) if i not in del_row]
網上的例子是用的index函數。在我寫過程中,發現這里有個坑。比如list中存在相同元素,就始終返回前一個匹配的索引,結果就會有問題。我就完全避免了index函數。不知道哪個是對的,目前滿足使用需要,將就着用了。有點小尷尬。
Case
BaseCase斷言:
def checkFlag(self, p, r):
"""預期,實際"""
err = str([p['url'], p['body'], r.text])
try:
b = False
if (r.json()['flag'] in [1, '1', '', None, 'statistic_by_result', 0,
"0", 'struct_product', 'v_select_jz_single']
or r.json()['message'] in ("暫無數據", "未查詢到數據")):
b = True
self.assertEqual(True, b, msg=err)
except (json.JSONDecodeError, KeyError): # 1.返回的不是json,比如下載、404 2.無flag
self.assertEqual(200, r.status_code, msg=err)
最簡單的一個測試用例:
from case.PyPlatform2_0_2.Public import *
class Home(BaseCase):
"""首頁"""
def setUp(self):
log(testname(self.__repr__()) + '\n')
record([testname(self.__repr__())])
def test(self):
"""xxx"""
self.req({
"url": full_url("xxx"),
"body": {}
})
setup,輸出日志。
Token
因為公司登陸用的token,跟cookie類似,保留登陸狀態,避免重復登陸。
如何處理token也是框架設計的一個要點。
Env設置token,因為每個系統的登陸參數值都不一樣。
Var.token = login.get_token()
BaseCase.req在每次請求時獲取token,從而免登錄。
if "headers" not in p.keys():
p['headers'] = {'token': ''}
p['headers']['token'] = Var.token
CSV
寫文件:
if not os.path.exists(path):
f = open(path, 'a', newline='')
a = csv.writer(f)
a.writerow(title)
f.close()
f = open(path, 'a', newline='')
a = csv.writer(f)
try:
a.writerow(d)
except UnicodeEncodeError:
d[4] = "Unicode隱藏" # response
a.writerow(d)
f.close()
if get_file_size(path) >= 50 * 1024 * 1024: # 超過50M刪除文件
os.remove(path)
record(title)
traceback自動生成文件名:
def _sys_name():
t = str(traceback.extract_stack())
b = True
for x in os.listdir(case_dir):
if x not in ["BaseCase.py", "__pycache__"]:
if x in t:
return x + "接口調用記錄" + current_date() + ".csv"
if b:
print("request找不到sysname")
print(t)
HTMLTestRunner
根據通用的版本,也是參考網上一些現有的美化代碼,綜合了一下,根據自己需求做了改造。
近20交易日測試通過率
加了一個echarts,把最近20交易日的測試通過率,通過折線走勢圖的方式展示出來。監測系統穩定性。
數據存放和讀取在data目錄的csv文件中。
統計表格
按項目進行分組統計,增加測試說明一列,按顏色區別測試結果狀態,可點擊查看詳細描述和錯誤信息。
同時優化了整體的樣式效果。
排序:
# 按照通過率從小到大排序
passrate_value = []
for key in passrate:
if key != 'total':
passrate_value.append(float(passrate[key].replace('%', '')))
passrate_value.sort()
保存折線圖數據:
today = datetime.datetime.now().strftime('%Y-%m-%d')
if '--' not in names: # 跑單個系統不存
if dao_is_trade_date(today): # 非交易日不存
with open(self.rct20_path, "r") as f: # 讀取數據
lines = csv.reader(f)
lines = list(lines)
for lin in lines:
lin[0] = lin[0].replace('月', '-')
lin[0] = lin[0].replace('日', '')
rct_data = lines
# print(rct_data)
nowdate = datetime.datetime.now().strftime('%m-%d')
# 如果有重復日期,先刪
l = len(rct_data)
while l != 0 and nowdate == rct_data[l - 1][0]:
rct_data.pop(l - 1)
l = len(rct_data)
for pt in self.passrate_tl:
n = pt[0]
v = pt[1]
row = []
row.append(str(nowdate))
row.append(str(n))
row.append(str(v).replace('%', ''))
rct_data.append(row)
# 只存近20條
row_20 = len(names) * 20
if len(rct_data) > row_20: # 超過20條
for i in range(0, len(names)):
rct_data.pop(0)
with open(self.rct20_path, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(rct_data)
拼接折線圖數據用於展示:
while ri < len(rct_data): # 遍歷 ->s_data
scan = []
while ri < len(rct_data) and rct_data[ri][0] == trade_date[di]:
s_data[rct_data[ri][1]].append(rct_data[ri][2])
scan.append(rct_data[ri][1])
ri += 1
chg = list(set(names) ^ set(scan)) # 差集
for c in chg:
s_data[c].append('--') # 增加/減少的項目,為'--'
di += 1
series = [] # 系列序列
s_names = s_data.keys()
for k in s_names:
s = {} # 單個系列
s['name'] = k
s['type'] = 'line'
if s_data != {}:
s['data'] = s_data[k]
series.append(s)
這部分代碼是很久之前寫的了,代碼應該是不夠簡潔、高效、規范滴,是可以優化滴。偷了懶沒有重構了。
用例設計
測試類型 | 描述 |
---|---|
冒煙測試 | 所有接口寫單獨的test,確保調用正常。 |
全選測試 | 將所有參數盡可能多的全選上,調用接口。 一定程序上可以彌補結對測試的不足。 |
結對測試 | 如前文所述,關注兩兩組合的情況。 |
參數值,部分采用隨機數。也視需求,從數據庫或其他接口獲取數據。
結束語
第一次寫技術博客。
馬上工作5年。
算是一個嘗試吧。
版權申明:本文為博主原創文章,轉載請保留原文鏈接及作者。