Python3簡易接口自動化測試框架設計與實現 實例2

目錄
  一、開發環境
  二、用到的模塊
  三、框架設計 ?3.一、流程
  3.二、項目結構
  五、日誌打印
  六、接口請求類封裝
  七、Excel數據讀取
  7.一、讀取配置文件
  7.一、編寫Excel操做類
  八、用例組裝
  九、用例運行結果校驗
  十、運行用例
  11 、小結
  一、開發環境
   操做系統:Ubuntu18
  開發工具:IDEA+PyCharm插件
   Python版本:3.6
   二、用到的模塊
  requests:用於發送請求
  xlrd:操做Excel,組織 測試用例
  smtplib,email:發送測試報告
  logging:日誌追蹤
  json:數據格式化
  Django:接口開發
  configparser:讀取配置文件
   三、框架設計
  3.一、流程
  接口用例是經過Excel來組織的,定義了URL,Request Body等列。執行流程以下:
  使用xlrd工具讀取Excel中的信息,拼接成一個個完整的請求。
  接口請求類拿到一個個完整的請求的去執行,這個過程須要 記錄日誌,每一次執行狀況都要有跡可循。
  回填測試結果,發送郵件,歸檔每次的運行結果。更好一點的作法是把歷史運行狀況作個報表,更直觀。
  優勢:
  用例經過Excel來組織,不須要編寫代碼,上手難度小。
  在用例個數不是不少的狀況,開發速度快。
  缺點:
  用例依賴是痛點。
  只能支持接口自動化用例。
  Excel中用例沒法預先檢查正確性,只有跑一把才能知道。
  沒法很好地管理大量用例,且不支持團隊協同做業,我的用來回歸測試或上線後的冒煙測試會是一個不錯的選擇。
  經過優缺點的對比,能夠明顯發現這個框架的硬傷其實不少了。因此不管是業界開源的 自動化測試框架或企業自研的尚未見過用Excel來組織用例的。值得一提的是個別企業自研的自動化框架很是難用,抑或是把一堆工具簡單組合到一塊兒。根本沒法提升團隊的生產力。不過好的產品也不是一蹴而就的,須要一個持續優化的過程。因此上面用Excel組織用例的框架仍是值的玩一玩的,暫且命名爲apitest吧。目前比較好的自動化測試框架有unittest,testng,pytest等。
   3.二、項目結構
  testcase:存放測試用例或請求的json文件。
  config:配置文件。
  report:測試報告和日誌文件及其歸檔。
  untils:工具集,send_request用來發送請求,email_tool用來發送郵件,excel_tool用來讀取Excel中的數據,check_result用來校驗結果,run_main用例執行入口,log_trace用來追蹤日誌。
五、日誌打印
  採用內置logging模塊才記錄運行日誌,設置日誌級別。
  log_trace.log:
   import  logging
  filename = "../report/test_case_run.log"
  logging.basicConfig(level=logging.INFO,
  format='%(asctime)s %(levelname)s1 %(filename)s [line:%(lineno)d]  %(message)s',
  datefmt='%a, %d %b %Y %H:%M:%S',
  filename=filename,
  filemode='w')
   六、接口請求類封裝
   安裝第三方模塊requests
 pip install requests
  定義函數send_request,根據傳入的方法類型分別去調用request的get,post,delete,put等方法去發送請求。send_request.py:
   import  requests
  from untils. log_trace import  *
  #發送get請求
  def get_request(url,data=None,headers=None):
  res = requests.get(url=url,data=data,headers=headers)
  return res
  #發送post請求
  def post_request(url,data,headers=None):
  res = requests.post(url=url,data=data,headers=headers)
  return res
  #發送delete請求
  def del_request(url,data=None,headers=None):
  res = requests.delete(url,data=data)
  return res
  #發送put請求
  def put_request(url,data,headers=None):
  pass
  def send_request(method,url,data=None,headers=None):
  try:
  logging.info(headers)
  if headers:
  if method == "GET":
  return get_request(url,data,headers=headers)
  if method == "POST":
  return post_request(url,data=data,headers=headers)
  if method == "DELETE":
  return  del_request(url,data=data,headers=headers)
  #put使用頻率低,暫時不寫
  if method == "PUT":
  return  put_request(url,data=data,headers=headers)
  else:
  logging.info("Header is null")
  except Exception as e:
  logging.info("send request fail:%s"%e)
  在untils_test.py中編寫代碼測試send_request方法,代碼以下:
   #coding:utf-8
  from untils.send_request import send_request
  def test_send_request():
  url="http://127.0.0.1:9000/articles/"
  headers = {
  "X-Token":"0a6db4e59c7fff2b2b94a297e2e5632e"
  }
  res = send_request("GET",url,headers=headers)
  print(res.json())
  if __name__ == "__main__":
  test_send_request()
  運行結果:
   /usr/bin/python3.6 /home/stephen/IdeaProjects/apitest/untils/untils_test.py
  {'status': 'BS.200', 'all_titles': {'amy1': 'alive', 'modifytest': 'alive', 'addTest': 'alive'}, 'msg': 'query articles sucess.'}
  Process finished with exit code 0
七、Excel數據讀取
  用例是放在Excel中的,用xlrd來讀取數據,寫數據須要用到xluntils,先安裝:
  pip install xlrd
  pip install xluntils
   7.一、讀取配置文件
  讀取Excel數據,咱們須要知道對應的行和列,列相對固定,在配置文件settings中定義,而後讀取,行做爲參數傳入。conf/settings文件中的定義以下:
[excel]
  case_no=0
  case_name=1
  is_run=2
  case_level=3
  case_header=4
  case_cookies=5
  req_type=6
  case_url=7
  case_body=8
  expect_result=9
  operator=10
  actual_result=11
  test_result=12
在unitls/load_conf.py中編寫讀取配置的方法,獲取各項列值的方法。lood_conf()函數須要傳入兩個參數:配置項字符串標識符,配置項類型。好比要讀取excel下整數case_url:lood_conf("excel.case_url","int")。class excel_config()下定義返回各項列值的方法。
  完整代碼以下:
 import  configparser
  '''
  read conf from setting.conf
  @:parameter:identstr,value_type
  value_type:"int" or "str"
  '''
  def lood_conf(identstr,value_type):
  cf = configparser.ConfigParser()
  cf.read("../config/settings.conf")
  idenlist = identstr.split('.')
  if value_type == "int":
  try:
  value = cf.getint(idenlist[0],idenlist[1])
  return  value
  except (configparser.NoSectionError ,configparser.NoOptionError) as e:
  print(e)
  if value_type == "str":
  try:
  value = cf.get(idenlist[0],idenlist[1])
  return value
  except (configparser.NoSectionError ,configparser.NoOptionError) as e:
  print(e)
  '''
  獲取url,request body等的列號
  '''
  class excel_config():
  #獲取用例編號的列
  def caseno_col(self):
  return lood_conf("excel.case_no","int")
  def casename_col(self):
  return lood_conf("excel.case_name","int")
  def isrun_col(self):
  #print(lood_conf("excel.is_run","int"))
  return lood_conf("excel.is_run","int")
  def level_col(self):
  return lood_conf("excel.case_level","int")
  def header_col(self):
  return lood_conf("excel.case_header","int")
  def cookies_col(self):
  return lood_conf("excel.case_cookies","int")
  def reqtype_col(self):
  return lood_conf("excel.req_type","int")
  def caseurl_col(self):
  return lood_conf("excel.case_url","int")
  def casebody_col(self):
  return lood_conf("excel.case_body","int")
  def expectresult_col(self):
  return lood_conf("excel.expect_result","int")
  def actualresult_col(self):
  return lood_conf("excel.actual_result","int")
  def testresult_col(self):
  return lood_conf("excel.test_result","int")
  def test_operator_col(self):
  return lood_conf("excel.operator","int")
7.一、編寫Excel操做類
  unitls/excel_tool.py中定義了獲取用例編號,用例名稱等方法,須要傳入行。回寫 測試結果,回寫實際結果方法須要傳入兩個參數:行,值。完整代碼以下:
   #coding:utf-8
  import xlrd
  from untils.log_trace import *
  from xlutils.copy import copy
  from untils.load_conf import excel_config
  class excel_tool():
  def __init__(self,excel_name):
  self.curr_excel = xlrd.open_workbook(excel_name)
  self.table = self.curr_excel.sheet_by_index(0)
  #print(self.table.cell(1,1).value)
  #實例化excel_config
  self.config = excel_config()
  self.rows = self.table.nrows
  self.excel_name = excel_name
#獲取用例編號
  def get_caseno(self,row):
  caseno = self.table.cell(row,self.config.caseno_col()).value
  if caseno:
  return caseno
  else:
  logging.info("case no is null")
  return None
  #獲取用例名稱
  def get_casename(self,row):
  casename = self.table.cell(row,self.config.casename_col()).value
  return casename
  #獲取是否運行標誌
  def get_runflag(self,row):
  run_flag = self.table.cell(row,self.config.isrun_col()).value
  return run_flag
  #獲取用例級別
  def get_caselevel(self,row):
  caselevel = self.table.cell(row,self.config.level_col()).value
  return caselevel
  #獲取請求url
  def get_caseurl(self,row):
  caseurl = self.table.cell(row,self.config.caseurl_col()).value
  return caseurl
  #獲取請求body
#獲取請求body
  def get_casebody(self,row):
  case_body = self.table.cell(row,self.config.casebody_col()).value
  return case_body
  #獲取header
  def get_headerflag(self,row):
  headerflag = self.table.cell(row,self.config.header_col()).value
  return headerflag
  #獲取coocikes
  def get_cookiesflag(self,row):
  cookiesflag = self.table.cell(row,self.config.cookies_col()).value
  return cookiesflag
  #獲取請求類型
  def get_methodtype(self,row):
  method_type = self.table.cell(row,self.config.reqtype_col()).value
  return method_type
  #獲取預期結果
  def get_expectres(self,row):
  expect_res = self.table.cell(row,self.config.expectresult_col()).value
  return expect_res
  #獲取測試結果
def get_testres(self,row):
  test_res= self.table.cell(row,self.config.testresult_col()).value
  return test_res
  #獲取操做符
  def get_operator(self,row):
  operator = self.table.cell(row,self.config.test_operator_col()).value
  return operator
  #回寫測試結果到excel
  def write_testres(self,row,value):
  wbook = copy(xlrd.open_workbook(self.excel_name))
  sheet = wbook.get_sheet(0)
  sheet.write(row, self.config.testresult_col(), value)
  wbook.save(self.excel_name)
  #回寫實際結果
  def write_actualres(self,row,value):
  wbook = copy(xlrd.open_workbook(self.excel_name))
  sheet = wbook.get_sheet(0)
  sheet.write(row, self.config.actualresult_col(), value)
  wbook.save(self.excel_name)
  八、用例組裝
  有了Excel操做類,就能夠方便讀取數據和回填結果了。接下來,在unitls/run_main.py中來組裝用例。組裝以前,先獲取是否運行的標誌:
  運行標誌爲N,不組裝,將用例標記爲skiiped,回填測試結果到Excel文件中。
  運行標誌爲Y,開始組裝用例並執行,並對比預期結果和實際結果。
  用例執行經過,將用例標記爲pass,回填測試結果和實際結果,實際結果爲接口的返回。
  用例執行失敗,將用例標記爲failed,回填測試結果和實際結果。
  接口鑑權須要用到的headers,先在run_main.py 中寫死,這個問題後面解決,在上面的過程當中,增長必要的日誌,方便定位問題和查看用例的運行日誌。完整代碼以下:
#coding:utf-8
  from untils.excel_tool import excel_tool
  from untils.send_request import send_request
  from untils.log_trace import *
  from untils.check_result import CheckResult
  import  json
  headers = {
  "X-Token":"0a6db4e59c7fff2b2b94a297e2e5632e"
  }
  class runner():
  def __init__(self):
  self.excel = excel_tool("../testcase/test.xls")
  self.check = CheckResult()
  def join_case(self):
  global  skip_list,sucess_list,failed_list,skip_list
  sucess_list = []
  sucess_list = []
  failed_list = []
  skip_list = []
  for row in range(1,self.excel.rows):
  no = self.excel.get_caseno(row)
  url = self.excel.get_caseurl(row)
  isrun = self.excel.get_runflag(row)
  name = self.excel.get_casename(row)
  level = self.excel.get_caselevel(row)
  data = self.excel.get_casebody(row)
  expect_res = self.excel.get_expectres(row)
  method = self.excel.get_methodtype(row)
  hasheader = self.excel.get_headerflag(row)
  operator = self.excel.get_operator(row)
  if isrun == "Y":
  logging.info("Begin to run test case : %s,case number :%s" %(name,no))
  logging.info("Request method type is :%s" %method)
  logging.info("Request URL:%s" %url)
  logging.info("Request Body:%s" %json.dumps(json.loads(data),sort_keys=True,indent=2))
  res = send_request(method,url,data=data,headers=headers)
  is_sucess = self.check.cmpdict(eval(expect_res),eval(res.text),operator)
  print(is_sucess)
  if is_sucess:
  sucess_list.append(name)
  #回寫測試結果
  self.excel.write_testres(row,"pass")
  #回寫實際結果
  self.excel.write_actualres(row,res.text)
  logging.info("Test case %s run sucess." %name)
  else:
  failed_list.append(name)
  print("fail",is_sucess)
  #回寫測試結果
  self.excel.write_testres(row,"failed")
  #回寫實際結果
  self.excel.write_actualres(row,res.text)
  logging.error("Test case %s run fail." %name)
  logging.info("Response is:%s" %json.dumps(res.json(),sort_keys=True,indent=2))
  else:
  skip_list.append(name)
  self.excel.write_testres(row,"skipped")
  def sum(self):
  total = len(sucess_list)+len(failed_list) + len(skip_list)
  failed = len(failed_list)
  sucess = len(sucess_list)
  logging.info("-----------------------------------------------------------")
  logging.info("本次一共運行:%s 個用例" %total)
  logging.info("本次運行經過:%s 個用例" %sucess)
  logging.info("本次運行跳過:%s 個用例" %len(skip_list))
  logging.info("跳過的用例:%s" %skip_list)
  logging.info("-----------------------------------------------------------")
   九、用例運行結果校驗
  在untils/run_main.py中方法cmpdict()是用來校驗預期和結果實際結果是否匹配,須要傳入三個參數:預期結果字典,實際結果字典,操做符。在check_result.py中編寫校驗用例結果的方法。目前只支持兩種操做符,equal和notequal,預期結果爲字典,其中不能嵌套字典。和完整代碼以下:
from untils.log_trace import *
  class  CheckResult():
  def dict_value(self,key,actual):
  try:
  if key in actual:
  return actual[key]
  else:
  for keys in actual:
  return self.dict_value(key,actual[keys])
  except Exception as e:
  logging.error(e)
  return None
  def cmpdict(self,expect,actual,equal):
  logging.info("Begin to check result of  testcase.")
  is_dict = isinstance(expect,dict) and isinstance(actual,dict)
  if is_dict:
  if equal == "equal":
  for key in expect.keys():
  if expect[key] == self.dict_value(key,actual):
  logging.info("%s is equal to %s" %(expect[key],self.dict_value(key,actual)))
  return True
  else:
 
logging.error("%s is not equal to %s" %(expect[key],self.dict_value(key,actual)))
  return False
  if equal == "notequal":
  for key in expect.keys():
  if key != self.dict_value(key,actual):
  logging.info("%s is not equal to %s" %(expect[key],self.dict_value(key,actual)))
  return True
  else:
  logging.error("%s is equal to %s" %(expect[key],self.dict_value(key,actual)))
  return False
  else:
  logging.error("Operator :%s is not support now,you can define it in file[check_result.py]" %equal)
  else:
  logging.error("Expect or actual  result is not dict,check it in  excel. ")
  十、運行用例
  新建一個名稱爲test.xls的Excel,將其放到testcase路徑下,並在Excel中編寫 測試用例。接口開發請參考:使用Django開發簡單接口: 文章增刪改查,我準備的用例以下:
 
  在untils/untils_test.py中導入run_mian模塊來測試一下:
   from untils.run_main import runner
  if __name__ == "__main__":
  #test_send_request()
  runner = runner()
  runner.join_case()
  runner.sum()
  
運行untils_test.py,而後去到Excel中查看運行結果:
report路徑下查看測試用例運行日誌,以下所示:
 Sat, 11 May 2019 19:37:56 INFO check_result.py [line:16]  Begin to check result of  testcase.
  Sat, 11 May 2019 19:37:56 ERROR check_result.py [line:38]  Operator :e1qual is not support now,you can define it in file[check_result.py]
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:37]  Begin to run test case : 查詢文章,case number :1.0
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:38]  Request method type is :GET
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:39]  Request URL:http://127.0.0.1:9000/articles
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:40]  Request Body:{}
  Sat, 11 May 2019 19:37:56 INFO send_request.py [line:25]  {'X-Token': '0a6db4e59c7fff2b2b94a297e2e5632e'}
  Sat, 11 May 2019 19:37:56 INFO check_result.py [line:16]  Begin to check result of  testcase.
  Sat, 11 May 2019 19:37:56 INFO check_result.py [line:22]  BS.200 is equal to BS.200
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:52]  Test case 查詢文章 run sucess.
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:62]  Response is:{
  "all_titles": {
  "Hello": "alive",
  "amy1": "alive",
  "modifytest": "alive",
  "useasge of ddt": "alive"
  },
  "msg": "query articles sucess.",
"status": "BS.200"
  }
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:37]  Begin to run test case : 新增文章,case number :2.0
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:38]  Request method type is :POST
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:39]  Request URL:http://127.0.0.1:9000/articles/
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:40]  Request Body:{
  "content": "useasge of ddt",
  "title": "useasge of ddt"
  }
  Sat, 11 May 2019 19:37:56 INFO send_request.py [line:25]  {'X-Token': '0a6db4e59c7fff2b2b94a297e2e5632e'}
  Sat, 11 May 2019 19:37:56 INFO check_result.py [line:16]  Begin to check result of  testcase.
  Sat, 11 May 2019 19:37:56 ERROR check_result.py [line:25]  BS.200 is not equal to BS.400
  Sat, 11 May 2019 19:37:56 ERROR run_main.py [line:60]  Test case 新增文章 run fail.
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:62]  Response is:{
  "msg": "title aleady exist,fail to publish.",
  "status": "BS.400"
  }
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:37]  Begin to run test case : 修改文章,case number :3.0
Sat, 11 May 2019 19:37:56 INFO run_main.py [line:38]  Request method type is :POST
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:39]  Request URL:http://127.0.0.1:9000/articles/7
  Sat, 11 May 2019 19:37:56 INFO run_main.py [line:40]  Request Body:{
  "content": "modify test",
  "title": "modify test"
  }
  Sat, 11 May 2019 19:37:56 INFO send_request.py [line:25]  {'X-Token': '0a6db4e59c7fff2b2b94a297e2e5632e'}
  Sat, 11 May 2019 19:37:57 INFO check_result.py [line:16]  Begin to check result of  testcase.
  Sat, 11 May 2019 19:37:57 ERROR check_result.py [line:25]  BS.200 is not equal to BS.300
  Sat, 11 May 2019 19:37:57 ERROR run_main.py [line:60]  Test case 修改文章 run fail.
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:62]  Response is:{
  "msg": "article is not exists,fail to modify.",
  "status": "BS.300"
  }
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:37]  Begin to run test case : 刪除文章,case number :4.0
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:38]  Request method type is :DELETE
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:39]  Request URL:http://127.0.0.1:9000/articles/7
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:40]  Request Body:{}
  Sat, 11 May 2019 19:37:57 INFO send_request.py [line:25]  {'X-Token': '0a6db4e59c7fff2b2b94a297e2e5632e'}
  Sat, 11 May 2019 19:37:57 INFO check_result.py [line:16]  Begin to check result of  testcase.
  Sat, 11 May 2019 19:37:57 ERROR check_result.py [line:25]  BS.200 is not equal to BS.300
  Sat, 11 May 2019 19:37:57 ERROR run_main.py [line:60]  Test case 刪除文章 run fail.
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:62]  Response is:{
  "msg": "article is not exists,fail to delete.",
  "status": "BS.300"
  }
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:74]  -----------------------------------------------------------
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:75]  本次一共運行:5 個用例
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:76]  本次運行經過:1 個用例
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:77]  本次運行跳過:1 個用例
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:78]  跳過的用例:['新增文章缺乏title']
  Sat, 11 May 2019 19:37:57 INFO run_main.py [line:79]  -----------------------------------------------------------
11 、小結
  框架終於能跑起來了,可是遺留的問題還不少。
  不少地方的代碼不夠健壯,這個後面慢慢優化。還有用例校驗支持的運算符比較少。
  發送郵件模塊待完成。
  Headers的問題如何解決?
  若是請求的body比較多,寫在Excel是否是很不美觀呀?這個能夠從固定地方讀取文件來完成。
  Excel中測試用例有沒有必填項呀?這個能夠在運行結果以前進行校驗,必填項缺乏,不運行。
  最關鍵的一點,若是第二個用例依賴於第一個用例的返回,用例依賴一直是個痛點,下一篇解決。
  還有不少問題好比,重試機制,耗時的用例設置超時時間,超時默認爲失敗等等.......
相關文章
相關標籤/搜索