欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Pytest+Yaml+Excel?接口自動化測試框架的實現(xiàn)示例

 更新時間:2022年01月21日 10:52:52   作者:測試之路king  
本文主要介紹了Pytest+Yaml+Excel?接口自動化測試框架,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下

一、框架架構(gòu)

在這里插入圖片描述

二、項目目錄結(jié)構(gòu)

在這里插入圖片描述

三、框架功能說明

解決痛點(diǎn):

  • 通過session會話方式,解決了登錄之后cookie關(guān)聯(lián)處理
  • 框架天然支持接口動態(tài)傳參、關(guān)聯(lián)靈活處理
  • 支持Excel、Yaml文件格式編寫接口用例,通過簡單配置框架自動讀取并執(zhí)行
  • 執(zhí)行環(huán)境一鍵切換,解決多環(huán)境相互影響問題
  • 支持http/https協(xié)議各種請求、傳參類型接口
  • 響應(yīng)數(shù)據(jù)格式支持json、str類型的提取操作
  • 斷言方式支持等于、包含、大于、小于、不等于等方
  • 框架可以直接交給不懂代碼的功能測試人員使用,只需要安裝規(guī)范編寫接口用例就行

框架使用說明:

  • 安裝依賴包:pip install -r requirements.txt
  • 框架主入口為 run.py文件
  • 編寫用例可以在Excel或者Yaml 文件里面,按照示例編寫即可,也可以在test_case 目錄下通過python腳本編寫case
  • 斷言或者提取參數(shù)都是通過jsonpath、正則表達(dá)式提取數(shù)據(jù)
  • 用例執(zhí)行時默認(rèn)讀取Exceltest_case 目錄下用例

四、核心邏輯說明

工具類封裝

assert_util.py 斷言工具類封裝

def assert_result(response: Response, expected: str) -> None:
    """ 斷言方法
    :param response: 實際響應(yīng)對象
    :param expected: 預(yù)期響應(yīng)內(nèi)容,從excel中或者yaml讀取、或者手動傳入
    return None
    """
    if expected is None:
        logging.info("當(dāng)前用例無斷言!")
        return

    if isinstance(expected, str):
        expect_dict = eval(expected)
    else:
        expect_dict = expected
    index = 0
    for k, v in expect_dict.items():
        # 獲取需要斷言的實際結(jié)果部分
        for _k, _v in v.items():
            if _k == "http_code":
                actual = response.status_code
            else:
                if response_type(response) == "json":
                    actual = json_extractor(response.json(), _k)
                else:
                    actual = re_extract(response.text, _k)
            index += 1
            logging.info(f'第{index}個斷言數(shù)據(jù),實際結(jié)果:{actual} | 預(yù)期結(jié)果:{_v} 斷言方式:{k}')
            allure_step(f'第{index}個斷言數(shù)據(jù)', f'實際結(jié)果:{actual} = 預(yù)期結(jié)果:{v}')
            try:
                if k == "eq":  # 相等
                    assert actual == _v
                elif k == "in":  # 包含關(guān)系
                    assert _v in actual
                elif k == "gt":  # 判斷大于,值應(yīng)該為數(shù)值型
                    assert actual > _v
                elif k == "lt":  # 判斷小于,值應(yīng)該為數(shù)值型
                    assert actual < _v
                elif k == "not":  # 不等于,非
                    assert actual != _v
                else:
                    logging.exception(f"判斷關(guān)鍵字: {k} 錯誤!")
            except AssertionError:
                raise AssertionError(f'第{index}個斷言失敗 -|- 斷言方式:{k} 實際結(jié)果:{actual} || 預(yù)期結(jié)果: {_v}')

case_handle.py Case數(shù)據(jù)讀取工具類

def get_case_data():
   case_type = ReadYaml(config_path + "config.yaml").read_yaml["case"]
   if case_type == CaseType.EXCEL.value:
       cases = []
       for file in [excel for excel in os.listdir(data_path) if os.path.splitext(excel)[1] == ".xlsx"]:
           data = ReadExcel(data_path + file).read_excel()
           name = os.path.splitext(file)[0]
           class_name = name.split("_")[0].title() + name.split("_")[1].title()
           gen_case(name, data, class_name)
           cases.extend(data)
       return cases
   elif case_type == CaseType.YAML.value:
       cases = []
       for yaml_file in [yaml for yaml in os.listdir(data_path) if
                         os.path.splitext(yaml)[1] in [".yaml", "yml"]]:
           data = ReadYaml(data_path + yaml_file).read_yaml
           name = os.path.splitext(yaml_file)[0]
           class_name = name.split("_")[0].title() + name.split("_")[1].title()
           gen_case(name, data, class_name)
           cases.extend(data)
       return cases
   else:
       cases = []
       for file in [excel for excel in os.listdir(data_path) if
                    os.path.splitext(excel)[1] in [".yaml", "yml", ".xlsx"]]:
           if os.path.splitext(file)[1] == ".xlsx":
               data = ReadExcel(data_path + file).read_excel()
               name = os.path.splitext(file)[0]
               cases.extend(data)
           else:
               data = ReadYaml(data_path + file).read_yaml
               name = os.path.splitext(file)[0]
               cases.extend(data)

           class_name = name.split("_")[0].title() + name.split("_")[1].title()
           gen_case(name, data, class_name)
       return cases

excel_handle.py 讀取Excel工具類

def get_case_data():
   case_type = ReadYaml(config_path + "config.yaml").read_yaml["case"]
   if case_type == CaseType.EXCEL.value:
       cases = []
       for file in [excel for excel in os.listdir(data_path) if os.path.splitext(excel)[1] == ".xlsx"]:
           data = ReadExcel(data_path + file).read_excel()
           name = os.path.splitext(file)[0]
           class_name = name.split("_")[0].title() + name.split("_")[1].title()
           gen_case(name, data, class_name)
           cases.extend(data)
       return cases
   elif case_type == CaseType.YAML.value:
       cases = []
       for yaml_file in [yaml for yaml in os.listdir(data_path) if
                         os.path.splitext(yaml)[1] in [".yaml", "yml"]]:
           data = ReadYaml(data_path + yaml_file).read_yaml
           name = os.path.splitext(yaml_file)[0]
           class_name = name.split("_")[0].title() + name.split("_")[1].title()
           gen_case(name, data, class_name)
           cases.extend(data)
       return cases
   else:
       cases = []
       for file in [excel for excel in os.listdir(data_path) if
                    os.path.splitext(excel)[1] in [".yaml", "yml", ".xlsx"]]:
           if os.path.splitext(file)[1] == ".xlsx":
               data = ReadExcel(data_path + file).read_excel()
               name = os.path.splitext(file)[0]
               cases.extend(data)
           else:
               data = ReadYaml(data_path + file).read_yaml
               name = os.path.splitext(file)[0]
               cases.extend(data)

           class_name = name.split("_")[0].title() + name.split("_")[1].title()
           gen_case(name, data, class_name)
       return cases

yaml_handle.py 讀取Yaml文件的工具類

class ReadYaml:

    def __init__(self, filename):
        self.filename = filename

    @property
    def read_yaml(self) -> object:
        with open(file=self.filename, mode="r", encoding="utf-8") as fp:
            case_data = yaml.safe_load(fp.read())
        return case_data

配置文件

config.yaml 配置信息

# 服務(wù)器器地址
host: http://localhost:8091/

case: 1 # 0代表執(zhí)行Excel和yaml兩種格式的用例, 1 代表Excel用例,2 代表 yaml文件用例

輸出目錄

日志輸出目錄

import logging
import time
import os


def get_log(logger_name):
    """
    :param logger_name: 日志名稱
    :return: 返回logger handle
    """
    # 創(chuàng)建一個logger
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.INFO)

    # 獲取本地時間,轉(zhuǎn)換為設(shè)置的格式
    rq = time.strftime('%Y%m%d', time.localtime(time.time()))

    # 設(shè)置所有日志和錯誤日志的存放路徑
    path = os.path.dirname(os.path.abspath(__file__))
    all_log_path = os.path.join(path, 'interface_logs\\All_Logs\\')
    if not os.path.exists(all_log_path):
        os.makedirs(all_log_path)

    error_log_path = os.path.join(path, 'interface_logs\\Error_Logs\\')
    if not os.path.exists(error_log_path):
        os.makedirs(error_log_path)

    # 設(shè)置日志文件名
    all_log_name = all_log_path + rq + '.log'
    error_log_name = error_log_path + rq + '.log'

    if not logger.handlers:
        # 創(chuàng)建一個handler寫入所有日志
        fh = logging.FileHandler(all_log_name, encoding='utf-8')
        fh.setLevel(logging.INFO)
        # 創(chuàng)建一個handler寫入錯誤日志
        eh = logging.FileHandler(error_log_name, encoding='utf-8')
        eh.setLevel(logging.ERROR)
        # 創(chuàng)建一個handler輸出到控制臺
        ch = logging.StreamHandler()
        ch.setLevel(logging.ERROR)

        # 以時間-日志器名稱-日志級別-文件名-函數(shù)行號-錯誤內(nèi)容
        all_log_formatter = logging.Formatter(
            '[%(asctime)s] %(filename)s - %(levelname)s - %(lineno)s - %(message)s')
        # 以時間-日志器名稱-日志級別-文件名-函數(shù)行號-錯誤內(nèi)容
        error_log_formatter = logging.Formatter(
            '[%(asctime)s] %(filename)s - %(levelname)s - %(lineno)s - %(message)s')
        # 將定義好的輸出形式添加到handler
        fh.setFormatter(all_log_formatter)
        ch.setFormatter(all_log_formatter)
        eh.setFormatter(error_log_formatter)

        # 給logger添加handler
        logger.addHandler(fh)
        logger.addHandler(eh)
        logger.addHandler(ch)

    return logger

報告目錄

執(zhí)行case后自動生成,執(zhí)行之前自動刪除

allure 數(shù)據(jù)目錄

執(zhí)行case后自動生成,執(zhí)行之前自動刪除

請求工具類

base_request.py 請求封裝工具類

class BaseRequest:
    session = None

    @classmethod
    def get_session(cls):
        if cls.session is None:
            cls.session = requests.Session()
        return cls.session

    @classmethod
    def send_request(cls, case: dict) -> Response:
        """
        處理case數(shù)據(jù),轉(zhuǎn)換成可用數(shù)據(jù)發(fā)送請求
        :param case: 讀取出來的每一行用例內(nèi)容
        return: 響應(yīng)對象
        """

        log.info("開始執(zhí)行用例: {}".format(case.get("title")))
        req_data = RequestPreDataHandle(case).to_request_data
        res = cls.send_api(
            url=req_data["url"],
            method=req_data["method"],
            pk=req_data["pk"],
            header=req_data.get("header", None),
            data=req_data.get("data", None),
            file=req_data.get("file", None)
        )
        allure_step('請求響應(yīng)數(shù)據(jù)', res.text)
        after_extract(res, req_data.get("extract", None))

        return res

    @classmethod
    def send_api(cls, url, method, pk, header=None, data=None, file=None) -> Response:
        """
        :param method: 請求方法
        :param url: 請求url
        :param pk: 入?yún)㈥P(guān)鍵字, params(查詢參數(shù)類型,明文傳輸,一般在url?參數(shù)名=參數(shù)值), data(一般用于form表單類型參數(shù))
        json(一般用于json類型請求參數(shù))
        :param data: 參數(shù)數(shù)據(jù),默認(rèn)等于None
        :param file: 文件對象
        :param header: 請求頭
        :return: 返回res對象
        """
        session = cls.get_session()
        pk = pk.lower()
        if pk == 'params':
            res = session.request(method=method, url=url, params=data, headers=header)
        elif pk == 'data':
            res = session.request(method=method, url=url, data=data, files=file, headers=header)
        elif pk == 'json':
            res = session.request(method=method, url=url, json=data, files=file, headers=header)
        else:
            raise ValueError('pk可選關(guān)鍵字為params, json, data')
        return res

pre_handle_utils.py 請求前置處理工具類

def pre_expr_handle(content) -> object:
    """
    :param content: 原始的字符串內(nèi)容
    return content: 替換表達(dá)式后的字符串
    """
    if content is None:
        return None

    if len(content) != 0:
        log.info(f"開始進(jìn)行字符串替換: 替換字符串為:{content}")
        content = Template(str(content)).safe_substitute(GLOBAL_VARS)
        for func in re.findall('\\${(.*?)}', content):
            try:
                content = content.replace('${%s}' % func, exec_func(func))
            except Exception as e:
                log.exception(e)
        log.info(f"字符串替換完成: 替換字符串后為:{content}")

        return content

after_handle_utils.py 后置操作處理工具類

def after_extract(response: Response, exp: str) -> None:
    """
    :param response: request 響應(yīng)對象
    :param exp: 需要提取的參數(shù)字典 '{"k1": "$.data"}' 或 '{"k1": "data:(.*?)$"}'
    :return:
    """
    if exp:
        if response_type(response) == "json":
            res = response.json()
            for k, v in exp.items():
                GLOBAL_VARS[k] = json_extractor(res, v)
        else:
            res = response.text
            for k, v in exp.items():
                GLOBAL_VARS[k] = re_extract(res, v)

代碼編寫case

test_demo.py 用例文件示例

@allure.feature("登錄")
class TestLogin:

    @allure.story("正常登錄成功")
    @allure.severity(allure.severity_level.BLOCKER)
    def test_login(self):
        allure_title("正常登錄")
        data = {
            "url": "api/login",
            "method": "post",
            "pk": "data",
            "data": {"userName": "king", "pwd": 123456}
        }

        expected = {
            "$.msg": "登錄成功!"
        }
        # 發(fā)送請求
        response = BaseRequest.send_request(data)
        # 斷言操作
        assert_result(response, expected)

程序主入口

run.py 主入口執(zhí)行文件

def run():
    # 生成case在執(zhí)行
    if os.path.exists(auto_gen_case_path):
        shutil.rmtree(auto_gen_case_path)

    get_case_data()

    if os.path.exists('outputs/reports/'):
        shutil.rmtree(path='outputs/reports/')

    # 本地調(diào)式執(zhí)行
    pytest.main(args=['-s', '--alluredir=outputs/reports'])
    # 自動以服務(wù)形式打開報告
    # os.system('allure serve outputs/reports')

    # 本地生成報告
    os.system('allure generate outputs/reports -o outputs/html --clean')
    shutil.rmtree(auto_gen_case_path)


if __name__ == '__main__':
    run()

執(zhí)行記錄

allure 報告

在這里插入圖片描述

在這里插入圖片描述

日志記錄

[2022-01-11 22:36:04,164] base_request.py - INFO - 42 - 開始執(zhí)行用例: 正常登錄
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 37 - 開始進(jìn)行字符串替換: 替換字符串為:bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 44 - 字符串替換完成: 替換字符串后為:bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 68 - 處理請求前url:bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 78 - 處理請求后 url:http://localhost:8091/bank/api/login
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 90 - 處理請求前Data: {'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,165] pre_handle_utils.py - INFO - 37 - 開始進(jìn)行字符串替換: 替換字符串為:{'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,166] pre_handle_utils.py - INFO - 44 - 字符串替換完成: 替換字符串后為:{'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,166] pre_handle_utils.py - INFO - 92 - 處理請求后Data: {'password': '123456', 'userName': 'king'}
[2022-01-11 22:36:04,166] pre_handle_utils.py - INFO - 100 - 處理請求前files: None
[2022-01-11 22:36:04,175] base_request.py - INFO - 53 - 請求響應(yīng)數(shù)據(jù){"code":"0","message":"success","data":null}
[2022-01-11 22:36:04,176] data_handle.py - INFO - 29 - 提取響應(yīng)內(nèi)容成功,提取表達(dá)式為: $.code 提取值為 0
[2022-01-11 22:36:04,176] assert_util.py - INFO - 49 - 第1個斷言數(shù)據(jù),實際結(jié)果:0 | 預(yù)期結(jié)果:0 斷言方式:eq
[2022-01-11 22:36:04,176] data_handle.py - INFO - 29 - 提取響應(yīng)內(nèi)容成功,提取表達(dá)式為: $.message 提取值為 success
[2022-01-11 22:36:04,176] assert_util.py - INFO - 49 - 第2個斷言數(shù)據(jù),實際結(jié)果:success | 預(yù)期結(jié)果:success 斷言方式:eq

到此這篇關(guān)于Pytest+Yaml+Excel 接口自動化測試框架的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)Pytest Yaml Excel 接口自動化 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python httpx庫入門指南(最新推薦)

    Python httpx庫入門指南(最新推薦)

    Httpx 是一個用于發(fā)送 HTTP 請求的 Python 庫,它提供了簡單易用的 API,可以輕松地發(fā)送 GET、POST、PUT、DELETE 等請求,并接收響應(yīng),下面介紹下Python httpx庫入門指南,感興趣的朋友一起看看吧
    2023-12-12
  • Python?pip更新的兩種方式詳解

    Python?pip更新的兩種方式詳解

    Pip是用于管理Python軟件包的常用命令,Pip命令還用于更新/升級已經(jīng)安裝的Python軟件包,下面這篇文章主要給大家介紹了關(guān)于Python?pip更新的兩種方式,需要的朋友可以參考下
    2023-02-02
  • 在PyCharm中安裝PaddlePaddle的方法

    在PyCharm中安裝PaddlePaddle的方法

    這篇文章主要介紹了在PyCharm中安裝PaddlePaddle的方法,本文給大家介紹的非常想詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • 用python做一個搜索引擎(Pylucene)的實例代碼

    用python做一個搜索引擎(Pylucene)的實例代碼

    下面小編就為大家?guī)硪黄胮ython做一個搜索引擎(Pylucene)的實例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • python中hasattr()、getattr()、setattr()函數(shù)的使用

    python中hasattr()、getattr()、setattr()函數(shù)的使用

    這篇文章主要介紹了python中hasattr()、getattr()、setattr()函數(shù)的使用方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • python實現(xiàn)百度語音識別api

    python實現(xiàn)百度語音識別api

    這篇文章主要為大家詳細(xì)介紹了python實現(xiàn)百度語音識別api,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • 簡單理解Python中基于生成器的狀態(tài)機(jī)

    簡單理解Python中基于生成器的狀態(tài)機(jī)

    這篇文章主要介紹了簡單理解Python中基于生成器的狀態(tài)機(jī),來自于IBM官方技術(shù)文檔,需要的朋友可以參考下
    2015-04-04
  • Django admin禁用編輯鏈接和添加刪除操作詳解

    Django admin禁用編輯鏈接和添加刪除操作詳解

    今天小編就為大家分享一篇Django admin禁用編輯鏈接和添加刪除操作詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • Python中定時器用法詳解之Timer定時器和schedule庫

    Python中定時器用法詳解之Timer定時器和schedule庫

    目前所在的項目組需要經(jīng)常執(zhí)行一些定時任務(wù),于是選擇使用 Python 的定時器,下面這篇文章主要給大家介紹了關(guān)于Python中定時器用法詳解之Timer定時器和schedule庫的相關(guān)資料,需要的朋友可以參考下
    2024-02-02
  • python中星號變量的幾種特殊用法

    python中星號變量的幾種特殊用法

    不知道大家知不知道在Python中,星號除了用于乘法數(shù)值運(yùn)算和冪運(yùn)算外,還有一種特殊的用法"在變量前添加單個星號或兩個星號",實現(xiàn)多參數(shù)的傳入或變量的拆解,本文將詳細(xì)介紹"星號參數(shù)"的用法。有需要的可以參考借鑒。
    2016-09-09

最新評論