apiAutoTest:自動化測試中的數據依賴處理


原始版本

簡書:https://www.jianshu.com/p/6bfaca87a93b
博客園:https://www.cnblogs.com/zy7y/p/13426816.html
testerhome:https://testerhome.com/topics/25003

最新用例截圖以及用例填寫格式

case.png

Snipaste_2020-11-23_07-21-53.png

數據依賴/路徑參數依賴

我理解的參數依賴/接口依賴就是接口進行關聯操作,比如有些查詢接口需要登錄之后才可以操作,那么我們就需要拿到token之類的東西,這一部分東西是放到header中的,apiAutoTest圍繞的只有路徑參數依賴,請求數據依賴

  • 路徑參數依賴

    譬如說現在的restful,一個users接口,路由一般這樣的users他的請求方式是get,這個路由我們把他認為是查所有用戶,如果查某一個用戶可能是這樣的users/:id也是個get請求,這里這個id想表達的意思是這里有個需要個用戶id的參數,比如1-500里面的任意1個,也就是說這個id是可變的,可以從登錄接口的返回響應取一個叫userId的值

  • 請求參數依賴

    這個應該好理解些,就是說支付接口需要的訂單id,是從上一步提交訂單接口返回的響應訂單id

舉個例子

假設現在有個實際響應結果字典如下

{"case_002": {
        "data": {
            "id": 500,
            "username": "admin",
            "mobile": "12345678",
        }},
  "case_005": {
        "data": {
            "id": 511,
            "create_time": 1605711095
        },
    }
}
  • excel中接口路徑內容:users/&$.case_005.data.id&/state/&$.case_005.data.careate_time&

    代碼內部解析后如下:users/511/state/1605711095

    &$.case_005.data.id& 代表從響應字典中提取case_005字典中data字典中的id的值,提取出來的結果是511

  • excel中請求參數內容如下:

    {
     "pagenum": 1, 
     "pagesize": "12",
     "data": &$.case_005.data&, 
     "userId": &$.case_002.data.id&
    }
    

    代碼內部解析后如下:

    {
     "pagenum": 1, 
     "pagesize": "12",
     "data": {
                "id": 511,
                "create_time": 1605711095
            }, 
     "userId": 500
    }
    

其實不難看出其中規則&jsonpath提取語法&,如果你需要的內容是字符串類型,只需要這樣"&jsonpath提取語法&"

上傳文件

用例中書寫格式,在上傳文件欄

# 單文件上傳在excel中寫法
{"接口中接受文件對象的參數名": "文件路徑地址"}

# 多文件上傳在excel中寫法
{"接口中接受文件對象的參數名": ["文件路徑1", "文件路徑2"]}

預期結果

用例書寫格式

# 斷言一個內容
{"jsonpath提取表達式": 預期結果內容}
# 多個斷言
{"jsonpath提取表達式1": 預期結果內容1,"jsonpath提取表達式2": 預期結果內容2}

其他優化

  • config.yaml文件中新增可配置初始header,整體代碼優化,相比之前,同樣測試用例執行下,快了2s左右
  • 將配置文件讀取,用例讀取整合在read_file.py
  • 移除報告壓縮方法
  • 減少日志信息

現依賴處理代碼

tools/init.py

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: __init__.py
@ide: PyCharm
@time: 2020/7/31
"""
import json
import re

import allure

from jsonpath import jsonpath
from loguru import logger


def extractor(obj: dict, expr: str = '.') -> object:
    """
    根據表達式提取字典中的value,表達式, . 提取字典所有內容, $.case 提取一級字典case, $.case.data 提取case字典下的data
    :param obj :json/dict類型數據
    :param expr: 表達式, . 提取字典所有內容, $.case 提取一級字典case, $.case.data 提取case字典下的data
    $.0.1 提取字典中的第一個列表中的第二個的值
    """
    try:
        result = jsonpath(obj, expr)[0]
    except Exception as e:
        logger.error(f'提取不到內容,丟給你一個錯誤!{e}')
        result = None
    return result


def rep_expr(content: str, data: dict, expr: str = '&(.*?)&') -> str:
    """從請求參數的字符串中,使用正則的方法找出合適的字符串內容並進行替換
    :param content: 原始的字符串內容
    :param data: 在該項目中一般為響應字典,從字典取值出來
    :param expr: 查找用的正則表達式
    return content: 替換表達式后的字符串
    """
    for ctt in re.findall(expr, content):
        content = content.replace(f'&{ctt}&', str(extractor(data, ctt)))
    return content


def convert_json(dict_str: str) -> dict:
    """
    :param dict_str: 長得像字典的字符串
    return json格式的內容
    """
    try:
        if 'None' in dict_str:
            dict_str = dict_str.replace('None', 'null')
        elif 'True' in dict_str:
            dict_str = dict_str.replace('True', 'true')
        elif 'False' in dict_str:
            dict_str = dict_str.replace('False', 'false')
        dict_str = json.loads(dict_str)
    except Exception as e:
        if 'null' in dict_str:
            dict_str = dict_str.replace('null', 'None')
        elif 'true' in dict_str:
            dict_str = dict_str.replace('true', 'True')
        elif 'False' in dict_str:
            dict_str = dict_str.replace('false', 'False')
        dict_str = eval(dict_str)
        logger.error(e)
    return dict_str


def allure_title(title: str) -> None:
    """allure中顯示的用例標題"""
    allure.dynamic.title(title)


def allure_step(step: str, var: str) -> None:
    """
    :param step: 步驟及附件名稱
    :param var: 附件內容
    """
    with allure.step(step):
        allure.attach(json.dumps(var, ensure_ascii=False, indent=4), step, allure.attachment_type.TEXT)

tools/data_process.py

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: data_process.py
@ide: PyCharm
@time: 2020/11/18
"""
from tools import logger, extractor, convert_json, rep_expr, allure_step
from tools.read_file import ReadFile


class DataProcess:
    response_dict = {}
    header = ReadFile.read_config('$.request_headers')
    have_token = header.copy()

    @classmethod
    def save_response(cls, key: str, value: object) -> None:
        """
        保存實際響應
        :param key: 保存字典中的key,一般使用用例編號
        :param value: 保存字典中的value,使用json響應
        """
        cls.response_dict[key] = value
        logger.info(f'添加key: {key}, 對應value: {value}')

    @classmethod
    def handle_path(cls, path_str: str) -> str:
        """路徑參數處理
        :param path_str: 帶提取表達式的字符串 /&$.case_005.data.id&/state/&$.case_005.data.create_time&
        上述內容表示,從響應字典中提取到case_005字典里data字典里id的值,假設是500,后面&$.case_005.data.create_time& 類似,最終提取結果
        return  /511/state/1605711095
        """
        # /&$.case.data.id&/state/&$.case_005.data.create_time&
        return rep_expr(path_str, cls.response_dict)

    @classmethod
    def handle_header(cls, token: str) -> dict:
        """處理header
        :param token: 寫: 寫入token到header中, 讀: 使用帶token的header, 空:使用不帶token的header
        return
        """
        if token == '讀':
            return cls.have_token
        else:
            return cls.header

    @classmethod
    def handler_files(cls, file_obj: str) -> object:
        """file對象處理方法
        :param file_obj: 上傳文件使用,格式:接口中文件參數的名稱:"文件路徑地址"/["文件地址1", "文件地址2"]
        實例- 單個文件: &file&D:
        """
        if file_obj == '':
            return
        for k, v in convert_json(file_obj).items():
            # 多文件上傳
            if isinstance(v, list):
                files = []
                for path in v:
                    files.append((k, (open(path, 'rb'))))
            else:
                # 單文件上傳
                files = {k: open(v, 'rb')}
        return files

    @classmethod
    def handle_data(cls, variable: str) -> dict:
        """請求數據處理
        :param variable: 請求數據,傳入的是可轉換字典/json的字符串,其中可以包含變量表達式
        return 處理之后的json/dict類型的字典數據
        """
        if variable == '':
            return
        data = rep_expr(variable, cls.response_dict)
        variable = convert_json(data)
        return variable

    @classmethod
    def assert_result(cls, response: dict, expect_str: str):
        """ 預期結果實際結果斷言方法
        :param response: 實際響應字典
        :param expect_str: 預期響應內容,從excel中讀取
        return None
        """
        expect_dict = convert_json(expect_str)
        index = 0
        for k, v in expect_dict.items():
            actual = extractor(response, k)
            index += 1
            logger.info(f'第{index}個斷言,實際結果:{actual} | 預期結果:{v} \n斷言結果 {actual == v}')
            allure_step(f'第{index}個斷言',  f'實際結果:{actual} = 預期結果:{v}')
            assert actual == v

源碼地址

master: 分支為最新代碼

version1.0: 分支為之前開源的代碼(通過字典迭代的方式來處理數據依賴)

Https://gitee.com/zy7y/apiAutoTest.git

Https://github.com/zy7y/apiAutoTest.git

后續打算

目前在公司正在做接口測試,說實話也是摸索着來,以上的優化項都是實際做的過程中突然想到的,然后就更新了

  • 接入用例前后置SQL, 前置SQL目前想的是現在項目中遇到的問題,有些接口沒有返回需要的數據,這里就要用前置SQL查詢的結果傳到請求數據里面了,后置SQL主要是請求后查看數據庫中的數據是否變動,形成數據庫斷言 2020/12/08 完成 , 至此 apiAutoTest 應該不會 再有大更新~~~ 謝謝 看這個Demo的人
  • 企業微信推送:目前項目中預想的效果,是后端人員提交代碼,自動部署之后,通過gitlab-ci 啟動測試代碼,進行接口測試完成之后采集allure中的測試結果一有異常/失敗用例就發送郵件並進行企業微信推送給領導 這個其實就是重新發送請求~
  • .... 就不說了還有很多優化項,能力不夠好好充電吧,~~

致謝

謝謝各位對apiAutoTest的幫助,謝謝~~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM