如有任何學習問題,可以添加作者微信:lockingfree
課程目錄
Python接口測試實戰1(上)- 接口測試理論
Python接口測試實戰1(下)- 接口測試工具的使用
Python接口測試實戰2 - 使用Python發送請求
Python接口測試實戰3(上)- Python操作數據庫
Python接口測試實戰3(下)- unittest測試框架
Python接口測試實戰4(上) - 接口測試框架實戰
Python接口測試實戰4(下) - 框架完善:用例基類,用例標簽,重新運行上次失敗用例
Python接口測試實戰5(上) - Git及Jenkins持續集成
Python接口測試實戰5(下) - RESTful、Web Service及Mock Server
更多學習資料請加QQ群: 822601020獲取
PDF下載:鏈接:https://pan.baidu.com/s/1OwAa8nl1eeBj8fcrgd3sBA 密碼:e9d8
本節內容
- 使用用例基類
- 自定義TestSuite
- collect-only的實現
- testlist的實現
- 用例tags的實現
- rerun-fails的實現
- 命令行參數的使用
更簡單的用例編寫
使用用例基類
因為每條用例都需要從excel中讀取數據,解析數據,發送請求,斷言響應結果,我們可以封裝一個BaseCase
的用例基礎類,對一些方法進行封裝,來簡化用例編寫
重新規划了test目錄,在test下建立case文件夾存放用例,建立suite文件夾存放自定義的
TestSuite
test_user_data.xlsx
中增加了一列data_type
,FORM
指表單格式請求,JSON
指JSON格式請求
項目test/case文件夾下新建basecase.py
import unittest
import requests
import json
import sys
sys.path.append("../..") # 統一將包的搜索路徑提升到項目根目錄下
from lib.read_excel import *
from lib.case_log import log_case_info
class BaseCase(unittest.TestCase): # 繼承unittest.TestCase
@classmethod
def setUpClass(cls):
if cls.__name__ != 'BaseCase':
cls.data_list = excel_to_list(data_file, cls.__name__)
def get_case_data(self, case_name):
return get_test_data(self.data_list, case_name)
def send_request(self, case_data):
case_name = case_data.get('case_name')
url = case_data.get('url')
args = case_data.get('args')
headers = case_data.get('headers')
expect_res = case_data.get('expect_res')
method = case_data.get('method')
data_type = case_data.get('data_type')
if method.upper() == 'GET': # GET類型請求
res = requests.get(url=url, params=json.loads(args))
elif data_type.upper() == 'FORM': # 表單格式請求
res = requests.post(url=url, data=json.loads(args), headers=json.loads(headers))
log_case_info(case_name, url, args, expect_res, res.text)
self.assertEqual(res.text, expect_res)
else:
res = requests.post(url=url, json=json.loads(args), headers=json.loads(headers)) # JSON格式請求
log_case_info(case_name, url, args, json.dumps(json.loads(expect_res), sort_keys=True),
json.dumps(res.json(), ensure_ascii=False, sort_keys=True))
self.assertDictEqual(res.json(), json.loads(expect_res))
簡化后的用例:
test/case/user/test_user_login.py
from test.case.basecase import BaseCase
class TestUserLogin(BaseCase): # 這里直接繼承BaseCase
def test_user_login_normal(self):
"""level1:正常登錄"""
case_data = self.get_case_data("test_user_login_normal")
self.send_request(case_data)
def test_user_login_password_wrong(self):
"""密碼錯誤登錄"""
case_data = self.get_case_data("test_user_login_password_wrong")
self.send_request(case_data)
test/case/user/test_user_reg.py
from test.case.basecase import BaseCase
from lib.db import *
import json
class TestUserReg(BaseCase):
def test_user_reg_normal(self):
case_data = self.get_case_data("test_user_reg_normal")
# 環境檢查
name = json.loads(case_data.get("args")).get('name') # 范冰冰
if check_user(name):
del_user(name)
# 發送請求
self.send_request(case_data)
# 數據庫斷言
self.assertTrue(check_user(name))
# 環境清理
del_user(name)
def test_user_reg_exist(self):
case_data = self.get_case_data("test_user_reg_exist")
name = json.loads(case_data.get("args")).get('name')
# 環境檢查
if not check_user(name):
add_user(name, '123456')
# 發送請求
self.send_request(case_data)
更靈活的運行方式
之前我們的run_all.py
只有運行所有用例一種選擇,我們通過增加一些功能,提供更靈活的運行策略
運行自定義TestSuite
項目test/suite文件夾下新建test_suites.py
import unittest
import sys
sys.path.append("../..")
from test.case.user.test_user_login import TestUserLogin
from test.case.user.test_user_reg import TestUserReg
smoke_suite = unittest.TestSuite() # 自定義的TestSuite
smoke_suite.addTests([TestUserLogin('test_user_login_normal'), TestUserReg('test_user_reg_normal')])
def get_suite(suite_name): # 獲取TestSuite方法
return globals().get(suite_name)
修改run_all.py
為run.py
,添加run_suite()
方法
import unittest
from lib.HTMLTestReportCN import HTMLTestRunner
from config.config import *
from lib.send_email import send_email
from test.suite.test_suites import *
def discover():
return unittest.defaultTestLoader.discover(test_case_path)
def run(suite):
logging.info("================================== 測試開始 ==================================")
with open(report_file, 'wb') as f:
HTMLTestRunner(stream=f, title="Api Test", description="測試描述", tester="卡卡").run(suite)
# send_email(report_file)
logging.info("================================== 測試結束 ==================================")
def run_all(): # 運行所用用例
run(discover())
def run_suite(suite_name): # 運行`test/suite/test_suites.py`文件中自定義的TestSuite
suite = get_suite(suite_name)
if suite:
run(suite)
else:
print("TestSuite不存在")
只列出所有用例(並不執行)
run.py
中添加
def collect(): # 由於使用discover() 組裝的TestSuite是按文件夾目錄多級嵌套的,我們把所有用例取出,放到一個無嵌套的TestSuite中,方便之后操作
suite = unittest.TestSuite()
def _collect(tests): # 遞歸,如果下級元素還是TestSuite則繼續往下找
if isinstance(tests, unittest.TestSuite):
if tests.countTestCases() != 0:
for i in tests:
_collect(i)
else:
suite.addTest(tests) # 如果下級元素是TestCase,則添加到TestSuite中
_collect(discover())
return suite
def collect_only(): # 僅列出所用用例
t0 = time.time()
i = 0
for case in collect():
i += 1
print("{}.{}".format(str(i), case.id()))
print("----------------------------------------------------------------------")
print("Collect {} tests is {:.3f}s".format(str(i),time.time()-t0))
按testlist用例列表運行
test文件夾下新建testlist.txt
,內容如下
test_user_login_normal
test_user_reg_normal
# test_user_reg_exist # 注釋后不執行
run.py
中添加
def makesuite_by_testlist(testlist_file): # test_list_file配置在config/config.py中
with open(testlist_file) as f:
testlist = f.readlines()
testlist = [i.strip() for i in testlist if not i.startswith("#")] # 去掉每行結尾的"/n"和 #號開頭的行
suite = unittest.TestSuite()
all_cases = collect() # 所有用例
for case in all_cases: # 從所有用例中匹配用例方法名
if case._testMethodName in testlist:
suite.addTest(case)
return suite
按用例標簽運行
由於TestSuite我們必須提前組裝好,而為每個用例方法添加上標簽,然后運行指定標簽的用例能更加靈活
遺憾的是,unittest並沒有tag相關功能,一種實現方案是:
def tag(tag):
if tag==OptionParser.options.tag: # 運行的命令行參數
return lambda func: func # 如果用例的tag==命令行指定的tag參數,返回用例本身
return unittest.skip("跳過不包含該tag的用例") # 否則跳過用例
用例標記方法
@tag("level1")
def test_a(self):
pass
這種方法在最后的報告中會出現很多skipped
的用例,可能會干擾到因其他(如環境)原因需要跳過的用例
我這里的實現方法是通過判斷用例方法中的docstring中加入特定的標簽來重新組織TestSuite的方式
run.py
中添加
def makesuite_by_tag(tag):
suite = unittest.TestSuite()
for case in collect():
if case._testMethodDoc and tag in case._testMethodDoc: # 如果用例方法存在docstring,並且docstring中包含本標簽
suite.addTest(case)
return suite
用例標記方法
class TestUserLogin(BaseCase):
def test_user_login_normal(self):
"""level1:正常登錄""" # level1及是一個標簽,放到docstring哪里都可以
case_data = self.get_case_data("test_user_login_normal")
self.send_request(case_data)
重新運行上次失敗用例
我們在每次執行后,通過執行結果result.failures獲取到失敗的用例,組裝成TestSuite並序列化到指定文件中,rerun-fails時,反序列化得到上次執行失敗的TestSuite, 然后運行
在run.py
中添加
import pickle
import sys
def save_failures(result, file): # file為序列化保存的文件名,配置在config/config.py中
suite = unittest.TestSuite()
for case_result in result.failures: # 組裝TestSuite
suite.addTest(case_result[0]) # case_result是個元祖,第一個元素是用例對象,后面是失敗原因等等
with open(file, 'wb') as f:
pickle.dump(suite, f) # 序列化到指定文件
def rerun_fails(): # 失敗用例重跑方法
sys.path.append(test_case_path) # 需要將用例路徑添加到包搜索路徑中,不然反序列化TestSuite會找不到用例
with open(last_fails_file, 'rb') as f:
suite = pickle.load(f) # 反序列化得到TestSuite
run(suite)
修改run.py
中的run()方法,運行后保存失敗用例序列化文件
def run(suite):
logging.info("================================== 測試開始 ==================================")
with open(report_file, 'wb') as f:
# 結果賦予result變量
result = HTMLTestRunner(stream=f, title="Api Test", description="測試描述", tester="卡卡").run(suite)
if result.failures: # 保存失敗用例序列化文件
save_failures(result, last_fails_file)
# send_email(report_file) # 從配置文件中讀取
logging.info("================================== 測試結束 ==================================")
使用命令行參數
命令行參數是我們通過命令行調用run.py
(執行入口文件)傳遞的一些參數,通過不同的參數,執行不同的運行策略,如python run.py --collect-only
我們通過optparser實現命令行參數:
在config/config.py
中添加
# 命令行選項
parser = OptionParser()
parser.add_option('--collect-only', action='store_true', dest='collect_only', help='僅列出所有用例')
parser.add_option('--rerun-fails', action='store_true', dest='rerun_fails', help='運行上次失敗的用例')
parser.add_option('--testlist', action='store_true', dest='testlist', help='運行test/testlist.txt列表指定用例')
parser.add_option('--testsuite', action='store', dest='testsuite', help='運行指定的TestSuite')
parser.add_option('--tag', action='store', dest='tag', help='運行指定tag的用例')
(options, args) = parser.parse_args() # 應用選項(使生效)
- '--conllect-only'是參數名,dest='collect-only'指存儲到 options.collect_only變量中,'store_true'指,如果有該參數,options.collect_only=True
- 'store'指將--testsuite='smoke_suite',參數的值'smoke_suite'存到options.testsuite變量中
命令行選項使用方法:
run.py
中添加:
from config.config import *
def main():
if options.collect_only: # 如果指定了--collect-only參數
collect_only()
elif options.rerun_fails: # 如果指定了--rerun-fails參數
rerun_fails()
elif options.testlist: # 如果指定了--testlist參數
run(makesuite_by_testlist(testlist_file))
elif options.testsuite: # 如果指定了--testsuite=***
run_suite(options.testsuite)
elif options.tag: # 如果指定了--tag=***
run(makesuite_by_tag(options.tag))
else: # 否則,運行所有用例
run_all()
if __name__ == '__main__':
main() # 調用main()
運行結果:
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --collect-only
1.user.test_user_login.TestUserLogin.test_user_login_normal
2.user.test_user_login.TestUserLogin.test_user_login_password_wrong
3.user.test_user_reg.TestUserReg.test_user_reg_exist
4.user.test_user_reg.TestUserReg.test_user_reg_normal
----------------------------------------------------------------------
Collect 4 tests is 0.006s
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --rerun-fails
.
Time Elapsed: 0:00:00.081812
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --testlist
..
Time Elapsed: 0:00:00.454654
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --testsuite=smoke_suite
..
Time Elapsed: 0:00:00.471255
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --tag=level1
.
Time Elapsed: 0:00:00.062273
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py
....
Time Elapsed: 0:00:00.663564
其他優化
1.按天生成log,每次執行生成新的報告
修改config/config.py
import time
today = time.strftime('%Y%m%d', time.localtime())
now = time.strftime('%Y%m%d_%H%M%S', time.localtime())
log_file = os.path.join(prj_path, 'log', 'log_{}.txt'.format(today)) # 更改路徑到log目錄下
report_file = os.path.join(prj_path, 'report', 'report_{}.html'.format(now)) # 更改路徑到report目錄下
2.增加send_email()開關
config/config.py
增加
send_email_after_run = False
修改run.py
from config.config import *
def run(suite):
logging.info("================================== 測試開始 ==================================")
with open(report_file, 'wb') as f: # 從配置文件中讀取
result = HTMLTestRunner(stream=f, title="Api Test", description="測試描述", tester="卡卡").run(suite)
if result.failures:
save_failures(result, last_fails_file)
if send_email_after_run: # 是否發送郵件
send_email(report_file)
logging.info("================================== 測試結束 ==================================")
發送最新報告的問題稍后解決
源碼地址: 鏈接:https://pan.baidu.com/s/1DLNSKN0KKuvSgo7gbGbMeg 密碼:994e
此為北京龍騰育才 Python高級自動化(接口測試部分)授課筆記
課程介紹
想要參加現場(北京)/網絡課程的可以聯系作者微信:lockingfree
- 高效學習,快速掌握Python自動化所有領域技能
- 同步快速解決各種問題
- 配套實戰項目練習