Python接口測試實戰4(下) - 框架完善:用例基類,用例標籤,從新運行上次失敗用例

若有任何學習問題,能夠添加做者微信:lockingfreehtml

課程目錄

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 Serverpython

更多學習資料請加QQ羣: 822601020獲取數據庫

本節內容json

  • 使用用例基類
  • 自定義TestSuite
  • collect-only的實現
  • testlist的實現
  • 用例tags的實現
  • rerun-fails的實現
  • 命令行參數的使用

更簡單的用例編寫

使用用例基類

由於每條用例都須要從excel中讀取數據,解析數據,發送請求,斷言響應結果,咱們能夠封裝一個BaseCase的用例基礎類,對一些方法進行封裝,來簡化用例編寫api

從新規劃了test目錄,在test下創建case文件夾存放用例,創建suite文件夾存放自定義的TestSuite
test_user_data.xlsx中增長了一列data_typeFORM指表單格式請求,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.pyapp

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.pyrun.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

  1. 高效學習,快速掌握Python自動化全部領域技能
  2. 同步快速解決各類問題
  3. 配套實戰項目練習
相關文章
相關標籤/搜索