上一篇:Python3簡易介面自動化測試框架設計與實現(上)
7、Excel資料讀取
用例是放在Excel中的,用xlrd來讀取資料,寫資料需要用到xluntils,先安裝:
pip install xlrd
pip install xluntils
7.1、讀取配置檔案
讀取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.1、編寫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
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)
8、用例組裝
有了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("-----------------------------------------------------------")
9、用例執行結果校驗
在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. ")
10、執行用例
新建一個名稱為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中測試用例有沒有必填項呀?這個可以在執行結果之前進行校驗,必填項缺少,不執行。
- 最關鍵的一點,如果第二個用例依賴於第一個用例的返回,用例依賴一直是個痛點,下一篇解決。
- 還有很多問題比如,重試機制,耗時的用例設定超時時間,超時預設為失敗等等.......
作者:秦無殤
轉載請說明出處:https://www.cnblogs.com/webDepOfQWS/,謝謝合作。