2.3 pytest自动化之动态生成

1. 根目录

#main.py
from utils.verify_file_hash import File_Operate
import pytest


def run():
    file_operate = File_Operate('./log/', 'md5.json')
    file_operate.create_md5()
    new_dict = file_operate.file_map_md5('./data/')
    file_operate.create_and_write(new_dict, './data/', './case/')


if __name__ == "__main__":
    import sys
    sys.path.append(r'.')
    run()
    pytest.main(['-s'])
#conftest.py
import pytest
from utils.http_request import http_request
from config.path import pathList
from config.config import setting
from utils.encrypto import encryption


@pytest.fixture(scope='class')
def getCookie():
    login_cfg = {
        "method": "post",
        "url": pathList['doLogin'],
        "headers": {
            "Content-Type": "application/json;charset=UTF-8"
        },
        "data": {
            "userName": setting['username'],
            "password": encryption(setting['password'], setting['pubkey'])
        },
        "verify": False
    }
    res = http_request(login_cfg)
    return res.headers.get('Set-Cookie')

# allure本身就给测试方法统计了时长,如果自定义统计,可以通过request获取上下文信息
@pytest.fixture(scope='function', autouse=True)
def timer(request):
    start = time.time()
    yield
    end = time.time()
    print(f"""classname:{request.cls.__name__}
    function_name:{request.function.__name__}
    request_name:{request.node.callspec.params['cfg']['name']}
    duration:{(end-start)/1000}s""")

2.utils目录

from typing import Dict


def assert_http_response(expected: Dict, actual: Dict, failed: Dict = None) -> None:
    """
    Asserts the HTTP response against the expected values.

    Args:
        expected: The expected values.
        actual: The actual values.
        failed: The failed values.

    Raises:
        AssertionError: If the expected values do not match the actual values.
    """
    if failed is None:
        failed = {}

    for key, value in expected.items():
        if key not in actual:
            failed[key] = value
        elif isinstance(value, Dict) and isinstance(actual[key], Dict):
            assert_http_response(value, actual[key], failed)
        elif value != actual[key]:
            failed[key] = value

    if failed:
        raise AssertionError(f"Failed assertions: {failed}")
#encrypto.py

from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA


import base64


def encryption(text: str, public_key: bytes) -> str:
    text = text.encode('utf-8')
    # 构建公钥对象
    cipher_public = PKCS1_v1_5.new(RSA.importKey(public_key))
    # 加密
    text_encrypted = cipher_public.encrypt(text)
    # base64编码,并转为字符串
    text_encrypted_base64 = base64.b64encode(text_encrypted).decode()
    return text_encrypted_base64

def format_string(cfg: dict, store: dict) -> dict:
    for k, v in cfg.items():
        if isinstance(v, dict):
            # Recursively call format_string for nested dictionaries
            cfg[k] = format_string(v, store)
        elif isinstance(v, str) and '{' in v and '}' in v:
            try:
                # Use string formatting to replace placeholders with values from the store dictionary
                cfg[k] = v.format(**store)
            except KeyError:
                # Handle the case where a key is missing from the store dictionary
                pass
    return cfg
import requests
from requests.models import Response
from config.config import setting
import urllib3

baseUrl = setting['baseUrl']
timeout = setting['timeout']
# Create a session object to reuse the underlying TCP connection
session = requests.Session()


def http_request(config: dict) -> Response:
    urllib3.disable_warnings()
    # Construct the full URL by combining the base URL and the endpoint URL
    url = f"{baseUrl}{config['url']}"
    headers = config['headers']
    verify = config['verify']
    data = config['data']

    try:
        # Send the HTTP request using the session object
        res = session.request(
            method=config['method'],
            url=url,
            headers=headers,
            verify=verify,
            timeout=timeout,
            json=data
        )
        return res
    except requests.exceptions.Timeout:
        # Handle the case where the request times out
        print('time out')
import json
import jsonpath
from requests import Response


def save(d, d1, j, store) -> None:
    """
    This function extracts the values from the JSON response using the given JSONPath expressions and stores them in the
    `store` dictionary. It uses the `jsonpath` library to extract the values.

    :param d: The dictionary containing the JSONPath expressions to extract the values.
    :param d1: The list of JSONPath expressions to extract the values.
    :param j: The JSON object to extract the values from.
    :param store: The dictionary to store the extracted values in.
    :return: None
    """
    if d1 is not None:
        for i in d1:
            temp = jsonpath.jsonpath(j, f'$..{i}')
            if isinstance(temp, list) and len(temp) == 1:
                li_as = d.get('as', None)
                if li_as is not None and (not li_as or i not in li_as):
                    store[i] = temp[0]
                elif i in li_as:
                    var = li_as[i]
                    store[var] = temp[0]
                else:
                    raise ValueError('the result of temp is not correct')


def parse(d: dict, r: Response, store=None) -> dict[str, object]:
    """
    This function extracts the values from the request and response JSON objects using the JSONPath expressions specified
    in the `d` dictionary. It then stores the extracted values in the `store` dictionary.

    :param d: The dictionary containing the JSONPath expressions to extract the values.
    :param r: The `Response` object containing the request and response JSON objects.
    :param store: The dictionary to store the extracted values in.
    :return: The `store` dictionary containing the extracted values.
    """
    if store is None:
        store = {}
    li_req = d.get('req_extract', None)
    if li_req:
        req_body = json.loads(r.request.body)
        save(d, li_req, req_body, store)
    li_res = d.get('res_extract', None)
    if li_res:
        res_json = r.json()
        save(d, li_res, res_json, store)
    return store
import os


class Template:
    @staticmethod
    def check_todo_file(file_path: str) -> bool:
        """
        检查文件内容中是否包含 '# TODO' 字符串

        Args:
            file_path (str): 文件路径

        Returns:
            bool: 如果包含 '# TODO' 字符串则返回 True,否则返回 False
        """
        with open(file_path, mode='r+', encoding='utf-8') as file:
            return '# TODO' in file.read()

    @staticmethod
    def create_test_file(file_path: str, target_path: str) -> None:
        """
        创建测试文件

        Args:
            file_path (str): 文件路径
            target_path (str): 目标路径
        """
        if Template.check_todo_file(file_path):
            print(f'Message: 发现 <TODO> 标记,文件 <{file_path}> 尚未完成')
            return

        file_name = os.path.basename(file_path).replace('.py', '')
        import_name = f'{file_name}_cfg'
        test_file_path = os.path.join(target_path, f'test_{file_name}.py')

        with open(test_file_path, mode='w+', encoding='utf-8') as file:
            file.write(f'''import pytest
import allure
from utils.http_request import http_request
from data.{file_name} import {import_name}
from utils.parse import parse
from utils.compare import assert_http_response
from utils.format import format_string

store = {{}}


@allure.suite('{file_name}')
class Test_{file_name.capitalize()}:

    @allure.sub_suite('{import_name}')
    @pytest.mark.parametrize('cfg', {import_name})
    def test_{file_name}(self, getCookie, cfg):  
        format_string(cfg, store)
        allure.dynamic.title(cfg['name'])
        allure.dynamic.parameter('cfg', cfg)
        cfg['config']['headers']['Cookie'] = getCookie
        res = http_request(cfg['config'])
        allure.dynamic.description(res.text)
        parse(cfg, res, store)
        assert_http_response(cfg['assert'], res.json())
''')
        print(f"Message: 文件 <{test_file_path}> 创建成功")

3.config目录

setting = {
    "dev": "",
    "test": "https://test.lan",
    "prd": "",
    "baseUrl": '',
    "timeout": 10,
    "username": 'test',
    "password": '123',
    "pubkey": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCv88UkwZVIU8xOCW4O+6PydsRH"
              "\nLpvZRrcwUm1xDWWQdGvYCuxlRqCJCdg6g37wFxSIjyui5yIsLwdx2ogpX214z22b\nThAtkZB70Igd"
              "/yDJxs8PbKxZ6It5zlZKWDBPo3IYy9+RY9i7DRYUFlMyhSgNJQAL\nr9bApW8bG8PiCRkrzwIDAQAB\n-----END PUBLIC KEY----- ",
}
#path.py

pathList = {
    "pubkey": "/api/auth/key",
    "doLogin": "/api/auth/login",
    "menu": "/api/grant/menu",
    "currentUser": "/api/auth/currentUser",
    "keyword": "/api/key"
}

4.data目录

#homepage.py

from config.path import pathList
from config.config import setting
from utils.encrypto import encryption
"""
    params:
    name:接口名称,可以在使用allure报告时,传给allure的title
    config:接口必须的字段
    as:给被提取的字段起别名
    assert:断言,可以同时断言多个字段
    req_extract:提取请求body中的字段,可以同时提取多个
    res_extract:提取响应中的字段,可以同时提取多个
    description:被提取的字段,可以在其下方的接口中以{字段名}的方式引用
"""
homepage_cfg = [
    {
        "name": "homepage",
        "config": {
            "method": "get",
            "url": pathList['currentUser'],
            "headers": {
                "Content-Type": "application/json;charset=UTF-8"
            },
            "data": {
            },
            "verify": False
        },
        "req_extract": [],
        "res_extract": ["userId"],
        "as": {
        },
        "assert": {"code": "0000"}
    },
    {
        "name": "menu",
        "config": {
            "method": "get",
            "url": pathList['menu'],
            "headers": {
                "Content-Type": "application/json;charset=UTF-8",
            },
            "data": {},
            "verify": False
        },
        "req_extract": [],
        "res_extract": [],
        "as": {
            'code': 'login_status'
        },
        "assert": {"code": "0000"}
    },
    {
        "name": "keyword",
        "config": {
            "method": "post",
            "url": pathList['keyword'],
            "headers": {
                "Content-Type": "application/json;charset=UTF-8",
            },
            "data": {"keyword": "te", "keywordName": "t"},
            "verify": False
        },
        "req_extract": ["keywordName"],
        "res_extract": ["data"],
        "as": {
        },
        "assert": {"code": "0000"}
    },
    {
        "name": "delete",
        "config": {
            "method": "delete",
            "url": pathList['keyword'] + "/{data}?name={keywordName}",
            "headers": {
                "Content-Type": "application/json;charset=UTF-8",
            },
            "data": {},
            "verify": False
        },
        "req_extract": [],
        "res_extract": [],
        "as": {
        },
        "assert": {"code": "0000"}
    }
]
#pubkey.py

from config.path import pathList

pubkey_cfg = [
    {
        "name": "pubkey",
        "config": {
            "method": "get",
            "url": pathList['pubkey'],
            "headers": {
                "Content-Type": "application/json;charset=UTF-8"
            },
            "data": {},
            "verify": False
        },
        "res_extract": ['code'],
        "assert": {"code": '0000'},
        "as": {
            "code": "id"
        }
    }
]

5.可以另外创建一个目录用来存储接口中body的数据,然后在data目录中引用

整体项目目录如下

case目录和log目录及里面的文件均为自动生成

生成示例:

case/test_homepage.py

import pytest
import allure
from utils.http_request import http_request
from data.homepage import homepage_cfg
from utils.parse import parse
from utils.compare import assert_http_response
from utils.format import format_string

store = {}


@allure.suite('homepage')
class Test_Homepage:

    @allure.sub_suite('homepage_cfg')
    @pytest.mark.parametrize('cfg', homepage_cfg)
    def test_homepage(self, getCookie, cfg):
        format_string(cfg, store)
        allure.dynamic.title(cfg['name'])
        allure.dynamic.parameter('cfg', cfg)
        cfg['config']['headers']['Cookie'] = getCookie
        res = http_request(cfg['config'])
        allure.dynamic.description(res.text)
        parse(cfg, res, store)
        assert_http_response(cfg['assert'], res.json())

 log/md5.json

{
  "homepage.py": "4b7901efc2f903329ab9a225c4c2f052",
  "pubkey.py": "592a34337edd854cd999c7cae95c1e31"
}
                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值