python3接口自動化測試框架

個人感覺使用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
歡迎大家提出改進意見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章