個人感覺使用unittest維護測試用例,使用HTMLTestRunner生成測試報告很不方便,於是自己寫了一個接口自動化,使用excel維護接口測試用例,簡單明瞭,測試完成後生成html測試報告,併發送郵件。
ATI(Automated Testing of Interfaces)
項目結構如下:
總體思路:
1、用excel維護測試用例;
2、讀取excel中的測試用例,按順序執行各接口;
3、判斷接口響應值,如果接口請求失敗,或響應值不正確,則將錯誤信息記錄下來;
4、將測試結果寫到一個html裏;
5、將測試結果通過郵件發送;
6、可以設置週期執行、定時執行、被測系統重啓後自動執行。
config.py
項目配置文件,項目相關的配置如下:
# 是否在Linux上使用,0爲在Windows上使用,1爲在Linux上使用
IS_LINUX = 1
# 日誌級別
LOG_LEVEL = 'INFO'
# 接口響應超時時間
TIMEOUT = 0.5
# 檢查端口是否存在間隔時間
SLEEP = 60
# ip地址和端口
IP = '127.0.0.1'
PORT = '8888'
# 請求頭
HEADERS = {}
# 測試用例路徑
TESTCASE_PATH = os.path.join(os.path.dirname(__file__), 'testCase', 'testCase.xlsx')
# 全局變量路徑
GLOBAL_VARIABLES = os.path.join(os.path.dirname(__file__), 'testCase', 'globalVariables.txt')
# 測試結果存放路徑
RESULT_PATH = os.path.join(os.path.dirname(__file__), 'result')
# 日誌路徑
LOG_PATH = os.path.join(os.path.dirname(__file__), 'result')
main.py
程序入口,控制程序運行方式,包括單次執行、週期執行、定時執行和服務重啓後立即執行,相關配置如下:
# 定時任務設置
# 0爲只執行一次,1爲每隔INTERVAL(單位s)執行一次,2爲每天TIMER_SET執行一次
# 在Linux和Windows上均可以設置爲0,1和2僅對Linux上有效
QUERY_TYPE = 0
# 執行間隔時間,單位爲秒
INTERVAL = 120
# 定時任務執行時間
TIMER_SET = '23:59:00'
# 服務重啓後是否執行。如果服務重新啓動,則立即執行,僅QUERY_TYPE爲1或2時有效,如果QUERY_TYPE爲1,INTERVAL將重新計算
IS_START = True
testCase
存放測試用例和全局變量,其中globalVariables.txt
如下:
username root
password 1234567
注:變量名和變量值之間必須有空格,且每行只能有一條數據
testCase.xlsx
爲測試用例,詳細如下:
1、用例ID:用例ID中的數字後續生成測試報告有用到,用於控制每行顏色;
2、用例名稱:用例名稱;
3、是否執行:控制該用例是否執行,0爲不執行,1爲執行;
4、優先級:暫未使用;
5、請求接口:請求接口全路徑,拼接結果 http://IP:PORT/eds/login;
6、協議:包括 http 和 https ;
7、請求參數:主要用於post請求傳參,如果傳固定值,可直接寫固定值,如果是變量,需要加‘<>’號;
如果是get請求中的url需要參數化,則需要在請求接口中變量的位置加‘{}’號,相對應的請求參數需要寫參數值,同樣用‘<>’號,如果需要傳多個參數,用英文逗號‘,'隔開;
8、獲取響應值中的某個字段的值:如果需要獲取當前接口響應值中的某個字段的值,作爲後面接口的輸入參數,例如:
如果返回值爲{"code":0,"msg":"success","data":1},如需獲取data字段的值,則直接寫data就可以了;
9、變量名:獲取響應值中的某個字段的值後,存儲的變量名,相同的變量名的值會覆蓋;
10、接口響應超時:接口響應超時時間,如果爲0或爲空,則默認爲配置文件裏配置的時間,單位爲秒;
11、預期結果 和 斷言內容 :如果接口的響應值包括斷言內容,則可以認爲接口測試通過;
如果斷言內容爲空,則會比較 預期結果 和接口真實響應值,預期結果必須爲json;
預期結果 和接口真實響應值比較時,會根據預期結果中的字段,比較接口真實響應值中對應字段的值,可以只比較部分字段;
12、測試結果:是否執行成功;
13、失敗原因:可能失敗原因,即程序運行過程中報錯信息;
14、備註
注:不同功能的接口可以放在不同的sheet裏
result
保存測試結果,和日誌文件
common
compare.py
用於預期結果和接口響應值比較。
def compare(self, new_json, raw_json):
"""
比較兩個json是否相等
遍歷 new_json 中的key和value,然後在 raw_json 中找對應字段中的值,判斷是否相等,
如果key在new_json中,但不在raw_json中,會報錯;如果key在raw_json中,但不在new_json中,不會報錯
"""
if isinstance(new_json, dict) and isinstance(raw_json, dict):
self.parser_dict(new_json, raw_json)
elif isinstance(new_json, list) and isinstance(raw_json, list):
self.parser_list(new_json, raw_json)
else:
self.flag = 0
self.reason = 'Type error'
return self.flag, self.reason
DatabaseController.py
用於從數據庫中讀取數據,以參數化接口傳參。該函數需要根據實際情況更改,如sql腳本和變量名。默認爲不使用數據庫中讀取數據。
def read_data_from_mysql(self):
"""
從MySQL數據庫中讀取數據
"""
import pymysql
db = pymysql.connect(cfg.MYSQL_IP, cfg.MYSQL_USERNAME, cfg.MYSQL_PASSWORD, cfg.MYSQL_DATABASE)
cursor = db.cursor()
sql = 'select name, password from user order by rand() limit 1;'
cursor.execute(sql)
res = cursor.fetchall()
for i in range(len(res)):
self.varibles.update({self.variable_name[i]: res[i][0]})
EmailController.py
用於發送郵件,相關配置如下,郵件賬號登陸密碼需要加密。
# 測試完成後是否自動發送郵件
IS_EMAIL = True
# 郵箱配置,qq郵箱爲smtp.qq.com
# 所用的發件郵箱必須開啓SMTP服務
SMTP_SERVER = 'smtp.sina.com'
# 發件人
SENDER_NAME = '張三'
SENDER_EMAIL = '[email protected]'
# 郵箱登陸密碼,經過base64編碼
PASSWORD = 'UjBWYVJFZE9RbFpIV1QwOVBUMDlQUT09'
# 收件人,對應 baidu_all.txt 文件,該文件爲郵件組名。
RECEIVER_NAME = 'baidu_all'
# RECEIVER_EMAIL = 'baidu_all.txt' 多個收件人用英文逗號分隔
RECEIVER_NAME = 'baidu_all’爲郵件上顯示的收件人,通常公司羣發郵件的收件人爲郵件組的名字,而郵件組裏包含很多個人的郵箱地址。因此收件人郵箱地址配置在郵件組名對應的txt文件裏,如baidu_all.txt,多個收件人用英文逗號隔開。
PwdEncrypt.pyc
和PwdEncrypt.py
文件一樣,區別是編譯成pyc文件,但是仍可以反編譯出來,pyc文件並不安全。這裏隨意寫的加密方法,可更改。emailServer
函數用於解密並登陸郵箱。
def emailServer(smtp_server, port, username, password):
def dencrypt(pwd):
s5 = base64.b64decode(pwd)
s4 = base64.b85decode(s5)
s4 = s5
s3 = base64.b64decode(s4)
s2 = base64.b64decode(s5)
s3 = s2
s1 = base64.b32decode(s3)
return s1.decode()
server = smtplib.SMTP_SSL(smtp_server, port)
server.login(username, dencrypt(password))
return server
GenerateKey.py
主要用於加密密碼,請使用其他安全度較高的加密方式。在GenerateKey.py
文件中先輸入密碼,執行完成後生成加密後的字符串,將該字符串配置到config.py
配置文件中。
def encrypt(pwd):
s1 = base64.b32encode(pwd.encode())
s2 = base64.b64encode(s1)
s1 = s2
s3 = base64.b16encode(s2)
s3 = s1
s4 = base64.b85encode(s2)
s5 = base64.b64encode(s3)
return s5.decode()
password = '123456'
print(encrypt(password))
ExcelController.py
用於讀取excel文件中的測試用例。
excel = xlrd.open_workbook(self.path) # 打開excel表格
sheets = excel.sheet_names() # 獲取excel中所有的sheet
for sheet in sheets:
table = excel.sheet_by_name(sheet) # 獲取sheet中的單元格
for i in range(1, table.nrows): # 遍歷所有非空單元格
if table.cell_value(i, 0): # 用例ID非空
caseId = table.cell_value(i, 0).strip() # 用例ID
if not int(table.cell_value(i, 2)):
logger.logger.info('用例Id {} 不執行,已跳過'.format(caseId))
continue
caseName = table.cell_value(i, 1).strip()
priority = int(table.cell_value(i, 3))
interface = table.cell_value(i, 4).strip()
protocol = table.cell_value(i, 5)
method = table.cell_value(i, 6)
data = self.compile(table.cell_value(i, 7))
key = table.cell_value(i, 8)
name = table.cell_value(i, 9)
timeout = table.cell_value(i, 10)
expectedResult = table.cell_value(i, 11)
assertion = table.cell_value(i, 12).strip()
if method == 'get' and data: # 如果是get請求,且有請求參數
request_data = data.split(',')
interface = interface.format(*request_data) # 直接將請求參數放到接口中
HtmlController.py
用於生成html格式的測試報告。
def writeHtml(self):
"""
生成html測試報告
"""
fail_case_num = len(self._fail_case) # 失敗用例數
all_case_num = len(self._all_case) # 所有用例數
success_rate = (1 - fail_case_num / all_case_num) * 100 # 計算成功率
spend_time = time.time() - self.start_time # 測試花費總時間
# 把所有用例連接起來,拼成完整的表格
fail_rows = ''.join(self._fail_case)
all_rows = ''.join(self._all_case)
# 失敗用例表格和所有用例表格分開保存
fail_table = self.table.format('{}{}'.format(self.table_head, fail_rows))
all_table = self.table.format('{}{}'.format(self.table_head, all_rows))
# 測試結果概覽詳情
detail = self.overview1.format(all_case_num, spend_time, all_case_num-fail_case_num, fail_case_num, success_rate)
# 測試報告標題
header = '{}{}{}{}'.format(self.title, self.test_time, self.overview, detail)
# 根據成功率決定郵件正文內容,生成失敗用例測試報告
if success_rate == 100:
fail_html = self.html.format('{}{}{}'.format(header, self.fail2, self.last))
else:
fail_html = self.html.format('{}{}{}{}'.format(header, self.fail1, fail_table, self.last))
# 生成所有用例測試報告
all_html = self.html.format('{}{}{}'.format(header, self.success, all_table))
# 將所有用例測試報告保存到本地
html_path = os.path.join(self.path, self.name + '.html')
with open(html_path, 'w') as f:
f.writelines(all_html)
logger.logger.info('所有用例測試結果保存成功。')
return fail_html, self.name
測試報告長的是這個樣子的
request.py
發起請求。目前僅支持get和post請求,其他請求可自行增加。
def request(self, method, protocol, interface, data, headers=None, timeout=None):
"""請求入口,目前僅支持get和post請求,其他請求可自行添加"""
if timeout is None:
timeout = cfg.TIMEOUT
if headers is None:
headers = cfg.HEADERS # 需要請求頭
try:
if method == 'get':
res = self.get(protocol, interface, timeout)
elif method == 'post':
res = self.post(protocol, interface, data, headers, timeout)
else:
logger.logger.error('暫不支持其他請求方式')
raise Exception('暫不支持其他請求方式')
return res
except Exception as err:
logger.logger.error(err)
raise Exception(err)
TxtToDict.py
主要用於處理globalVariables.txt
文件
Testing.py
所有測試操作都在這裏進行。
項目地址:https://github.com/leeyoshinari/ATI
歡迎大家提出改進意見。