I.TestCase作用:是最小的測試單元,用於檢查特定輸入集合的特定返回值,可以用來創建新的測試用例
II.編寫測試用例規則
(1)創建一個測試類,必須繼承unnittest模塊的TestCase類
(2)創建一個測試方法,必須以"test"開頭
(3)調用被測試類,傳入初始化數據
(4)調用被測試方法,得到計算結果。用assertEqual()斷言是否與預期結果相同。
(5)調用unnitest的main()執行測試用例
.:一條運行通過的測試用例 F:一條運行失敗的測試用例 E:一條運行錯誤的測試用例 s:一條運行跳過的測試用例
III.定義測試方法:以test 開頭的方法就是一條測試用例
(1)准備用例數據:用例的參數(datas)和預期結果(excepted)
(2)執行功能函數,獲取實際結果:result = res['code']
(3)對比實際結果和預期結果:self.assertEqual(excepted, result)【用例執行通沒通過的評判標准:斷言異常】
檢查 | 版本 | |
---|---|---|
assertEqual(a, b) | a == b是否相等 | |
assertNotEqual(a, b) | a != b | |
assertTrue(x) | bool(x)is True布爾值判斷 | |
assertFalse(x) | bool(x)is False | |
assertIs(a, b) | a is b 身份判斷:是否同一個對象判斷 | 3.1 |
assertIsNot(a, b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a, b) | a in b 成員判斷 | 3.1 |
assertNotIn(a, b) | a not in b | 3.1 |
assertIsInstance(a, b) | isinstance(a, b) | 3.2 |
assertNotIsInstance(a, b) | not isinstance(a, b) |
IV.測試用例包含內容
(1)數據處理
- 讀取excel數據
- 數據驅動:ddt方法
- 替換excel數據:header加token,可變數據替換(正則替換)
- excel數據格式處理:比如str轉json-->eval,編號str轉為int
(2)錯誤寫入日志
(3)執行結果和對比結果寫入excel
(4)查詢數據庫結果
完整代碼
import os import unittest from library.ddt import ddt,data # ddt數據驅動 from com.doexcel import DoExcel # 操作excel數據 from com.contants import DATA_DIR # 測試用例模塊所在目錄 from com.myconf import conf #讀取配置文件 from com.log import my_log # 日志處理對象 from com.handle_data import Header,replace_data,TestData # 數據處理 from com.handle_request import HandleRequest # http請求方法 from com.mysql import MySql # 導入數據庫 excel_path = os.path.join(DATA_DIR,"testexcel.xlsx") # excel路徑 @ddt class TestClassName(unittest.TestCase): excel = DoExcel(excel_path, "sheetname") cases = excel.read_data() # 讀取excel數據 http = HandleRequest() # 創建http請求對象 mysql = MySql() # 創建數據庫對象 @classmethod def setUpClass(cls): my_log.info("---------------開始執行TestClassName類測試用例---------") def setUp(self): pass # 每條用例執行之前都會執行 @data(*cases) def test_methodName(self,case): # -----------------------第一步:准備用例數據------------------------------------- # 用例方法參數 # 請求url url = conf.get('url_info', 'url_base')+ case["url"] # 請求方法 method = case["method"] # 數據替換 case['data'] = replace_data(case['data']) # excel中讀的數據類型str轉為json data = eval(case["data"]) # 請求頭 headers = getattr(Header, 'headers') # 將token加到請求頭中 headers['Authorization'] = getattr(TestData, 'token') # 預期結果 expected = eval(case['expected']) # 該用例在表單中所在行 row = int(case['case_id']) + 1 #-----------------------第二步:發送請求到接口,獲取實際結果------------------------------------- result = self.http.send(url = url,method = method,json = data,headers = headers).json() TestResult = 'FAIL' # 測試對比結果:默認為失敗 try: self.assertEqual(expected ['code'], result['code']) TestResult = 'PASS' my_log.info("執行用例:{0}--->執行通過".format(case["title"])) # 將測試結果寫入日志文件 except AssertionError as e: my_log.info("執行用例:{0}--->未執行通過,出錯位置為:{1}".format(case["title"], e)) # 將測試報錯結果寫入日志文件 raise e finally: self.excel.write_data(row = row, column = 9,value = str(result)) # 回寫執行結果 self.excel.write_data(row = row,column = 9,value = TestResult) # 回寫比對結果 def tearDown(self): pass # 每條用例執行之后都會執行 @classmethod def tearDownClass(cls): my_log.info("---------------結束執行TestClassName類測試用例---------") if __name__ == '__main__': unittest.main()
說明:
- 配置文件涉及內容:
[url_info] url_base = url content-type = application/json; charset=UTF-8
[test_info]
....
- ddt實現接口報告中按接口名顯示用例(修改ddt中的mk_test_name())
def mk_test_name(name, value, index=0): """ Generate a new name for a test case. It will take the original test name and append an ordinal index and a string representation of the value, and convert the result into a valid python identifier by replacing extraneous characters with ``_``. We avoid doing str(value) if dealing with non-trivial values. The problem is possible different names with different runs, e.g. different order of dictionary keys (see PYTHONHASHSEED) or dealing with mock objects. Trivial scalar values are passed as is. A "trivial" value is a plain scalar, or a tuple or list consisting only of trivial values. """ # Add zeros before index to keep order index = "{0:0{1}}".format(index + 1, index_len) # 添加了對字典數據的處理 if not is_trivial(value) and type(value) is not dict: #如果不符合value的要求,則直接返回用例名稱_下標作為最終測試用例名字 return "{0}_{1}".format(name, index) # 如果數據是字典,則獲取字典當中的api_name對應的值,加到測試用例名稱中 if type(value) is dict: try: value = value["case_name"] #case_name作為value值 except: return "{0}_{1}".format(name, index) try: value = str(value) except UnicodeEncodeError: # fallback for python2 value = value.encode('ascii', 'backslashreplace') test_name = "{0}_{1}_{2}".format(name, index, value) return re.sub(r'\W|^(?=\d)', '_', test_name)
- 數據處理文件
import re from com.myconf import conf import random # 請求頭 class Header: headers = {'X-Lemonban-Media-Type': conf.get('Content-Type': conf.get('url_info','Content-Type'),'Authorization': None} class TestData: """專門用來保存一些替換的數據""" token = None def replace_data(data): # 判斷是否有需要替換的數據 while re.search("#(.+?)#", data): # .匹配任意字符,+重復一次或多次,?匹配0次或1次(變成非貪婪模式)()匹配分組:在匹配的數據中提取數據 key = re.search("#(.+?)#", data).group(0) # 字典的鍵 value = re.search("#(.+?)#", data).group(1) # 字典的值 try: data = data.replace(key, conf.get('test_info', value)) # 替換信息為conf.ini中的值 except: data = data.replace(key,getattr(TestData,value)) # 如果滿足條件,且配置文件中沒有配置數據,則讀取臨時變量的值 return data