Python automated test cases: how to elegantly complete Json format data assertions

Table of contents

foreword

Use directly

optimization

encapsulation

summary

Advanced

Summarize

 Data acquisition method


foreword

Record the advanced application of Json assertions in the work.

Use directly

I wrote a blog a long time ago, recording the data obtained at the time for a specified key in a multi-level json:

#! /usr/bin/python
# coding:utf-8 
""" 
@author:Bingo.he 
@file: get_target_value.py 
@time: 2017/12/22 
"""
def get_target_value(key, dic, tmp_list):
    """
    :param key: 目标key值
    :param dic: JSON数据
    :param tmp_list: 用于存储获取的数据
    :return: list
    """
    if not isinstance(dic, dict) or not isinstance(tmp_list, list):  # 对传入数据进行格式校验
        return 'argv[1] not an dict or argv[-1] not an list '
    if key in dic.keys():
        tmp_list.append(dic[key])  # 传入数据存在则存入tmp_list
    for value in dic.values():  # 传入数据不符合则对其value值进行遍历
        if isinstance(value, dict):
            get_target_value(key, value, tmp_list)  # 传入数据的value值是字典,则直接调用自身
        elif isinstance(value, (list, tuple)):
            _get_value(key, value, tmp_list)  # 传入数据的value值是列表或者元组,则调用_get_value
    return tmp_list


def _get_value(key, val, tmp_list):
    for val_ in val:
        if isinstance(val_, dict):  
            get_target_value(key, val_, tmp_list)  # 传入数据的value值是字典,则调用get_target_value
        elif isinstance(val_, (list, tuple)):
            _get_value(key, val_, tmp_list)   # 传入数据的value值是列表或者元组,则调用自身

optimization

Later, in the process of writing use case generation, it was found that there were many repeated assertion description information, and most of the returned data were actually standard json, so put the entire returned json into the assertion data:

continue_run_flag = True


def assert_json(base, juge, contain=(), reg=()):
    # 返回值,不符合预期时会设置为False
    flag = True
    for key, value in base.items():

        # 不进行断言的数据不进一步处理
        if key not in juge:
            continue

        if key in COMMON_RE_CHECK:

            if not re.match(COMMON_RE_CHECK[key], base[key]):
                flag = False
                logger.error("字段[{}]使用通用的正则匹配,不符合预期:预期正则匹配【{}】== 【{}】".format(key, COMMON_RE_CHECK[key], juge[key]))
            else:
                logger.warning("字段【{}】使用通用字段的正则匹配, 符合预期".format(key))
            continue

        if key in contain:
            if str(value) not in juge[key]:
                flag = False
                logger.error("字段[{}]不符合预期:预期【{}】包含 【{}】".format(key, juge[key], value))
                continue
            logger.info("字段[{}]符合预期:预期[{}] 包含 [{}]".format(key, juge[key], value))
            continue

        if key in reg:
            if not re.match(juge[key], value):
                flag = False
                logger.error("字段[{}]不符合预期:预期正则匹配【{}】== 【{}】".format(key, value, juge[key]))
                continue
            logger.info("字段[{}]断言成功:预期[{}]== 实际[{}]".format(key, value, juge[key]))
            continue
        if isinstance(value, str) or isinstance(value, int):
            if juge[key] != value:
                logger.error("字段[{}]不符合预期:预期【{}】!= 实际【{}】".format(key, value, juge[key]))
                flag = False
                continue
        elif isinstance(value, dict):
            assert_json(value, juge[key], contain=contain, reg=reg)
        elif isinstance(value, list):
            for i, v in enumerate(value):
                if isinstance(value, str) or isinstance(value, int):
                    if v != juge[key][i]:
                        logger.error("字段[{}]不符合预期:预期【{}】!= 实际【{}】".format(key, value, juge[key]))
                    else:
                        logger.info("字段[{}]断言成功:预期[{}]== 实际[{}]".format(key, value, juge[key]))
                elif isinstance(value, dict):
                    assert_json(value[i], juge[key][i], contain=contain, reg=reg)
                else:
                    assert_json(value[i], juge[key][i], contain=contain, reg=reg)

        else:
            pass

        logger.info("字段[{}]符合预期: 预期[{}]== 实际[{}]".format(key, value, juge[key]))

    # 失败是否继续执行,默认为TRUE
    if not continue_run_flag:
        assert flag

    return flag

transfer:

rsp = requests.get('http://localhost:8800/get', params={"name": "bingo", "age": 18}).json()
assert_json(rsp, {
    "args": {
        "name": "bingo"
    },
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Cache-Control": "max-age=259200",
        "Host": "httpbin.org",
        "User-Agent": "python-requests/2.27.1",
        "X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"
    },
    "req_param": [
        {
            "name": "bingo",
            "age": "18"
        }
    ],
    "origin": r"",
    "url": "http://httpbin.org/get?name=bingo"
},
contain=(), reg=("X-Amzn-Trace-Id", "origin",))

Log effect:

2022-05-05 14:25:49.967 | INFO     | __main__:assert_json:173 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[args]符合预期: 预期[{'name': 'bingo'}]== 实际[{'name': 'bingo'}]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Accept]符合预期: 预期[*/*]== 实际[*/*]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Accept-Encoding]符合预期: 预期[gzip, deflate]== 实际[gzip, deflate]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Cache-Control]符合预期: 预期[max-age=259200]== 实际[max-age=259200]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Host]符合预期: 预期[httpbin.org]== 实际[httpbin.org]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[User-Agent]符合预期: 预期[python-requests/2.27.1]== 实际[python-requests/2.27.1]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:149 - 字段[X-Amzn-Trace-Id]断言成功:预期[Root=1-62734553-430db0707e1a3656043cd165]== 实际[Root=\d-\w{8}-\w{24}]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[headers]符合预期: 预期[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62734553-430db0707e1a3656043cd165'}]== 实际[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=\\d-\\w{8}-\\w{24}'}]
2022-05-05 14:25:49.969 | WARNING  | __main__:assert_json:133 - 字段【origin】使用通用字段的正则匹配, 符合预期
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[age]符合预期: 预期[18]== 实际[18]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[req_param]符合预期: 预期[[{'age': '18', 'name': 'bingo'}]]== 实际[[{'name': 'bingo', 'age': '18'}]]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[url]符合预期: 预期[http://httpbin.org/get?name=bingo]== 实际[http://httpbin.org/get?name=bingo]

encapsulation

Simply encapsulate the method into the calling class:

class HttpBin:
    def __init__(self):
        self.continue_run_flag = True  # 失败是否继续执行
        self.base_url = 'http://localhost:8800'
        self.base_param = {"local_class": self.__class__.__name__}

    def get(self, param):
        path = "/get"
        param.update(self.base_param)
        self.rsp = requests.get(self.base_url + path, params=param)
        self.ans = self.rsp.json()
        logger.info(json.dumps(self.rsp.json(), indent=4))
        return self

    def set(self, param):
        path = "/set"
        param.update(self.base_param)
        self.rsp = requests.get(self.base_url + path, params=param)
        self.ans = self.rsp.json()
        logger.info(json.dumps(self.rsp.json(), indent=4))
        return self

    def assert_statusCode(self, result_code):
        """
        :param result_code: 包含关系断言
        :return: bool <= self.rsp.resultinfo
        """
        # 返回值,不符合预期时会设置为False
        flag = True

        if int(result_code) != self.rsp.status_code:
            logger.error(f"返回状态码[result_code]不符合预期:预期【{result_code}】!= 实际【{self.rsp.status_code}】")
            flag = False
        else:
            logger.info(f"返回状态码[result_code]符合预期:预期【{result_code}】!= 实际【{self.rsp.status_code}】")

        if not self.continue_run_flag:
            assert flag

        return self

    def assert_json_body(self, base, juge, contain=(), reg=()):
    ...

use case call

# 如果仅仅断言状态码
HttpBin().get({"name": "bingo", "age": 18}).assert_statusCode(200)

# 级连调用多个接口,使用同一个初始化数据
HttpBin().get({"name": "bingo", "age": 18}).assert_statusCode(200).\
        set({"name": "he", "age": 18}).assert_statusCode(200).assert_json_body(
        juge={
            "args": {
                "name": "bingo"
            },
            "headers": {
                "Accept": "*/*",
                "Accept-Encoding": "gzip, deflate",
                "Cache-Control": "max-age=259200",
                "Host": "httpbin.org",
                "User-Agent": "python-requests/2.27.1",
                "X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"
            },
            "req_param": [
                {
                    "name": "he",
                    "age": "18"
                }
            ],
            "origin": r"",
            "url": "http://httpbin.org/set?name=bingo"
        }, contain=(), reg=("X-Amzn-Trace-Id", "origin",)
    )

running result:

2022-05-05 19:39:36.951 | INFO     | __main__:assert_statusCode:53 - 返回状态码[result_code]符合预期:预期【200】!= 实际【200】
2022-05-05 19:39:36.951 | INFO     | __main__:assert_json_body:117 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 19:39:36.951 | INFO     | __main__:assert_json_body:117 - 字段[args]符合预期: 预期[{'name': 'bingo'}]== 实际[{'name': 'bingo'}]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Accept]符合预期: 预期[*/*]== 实际[*/*]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Accept-Encoding]符合预期: 预期[gzip, deflate]== 实际[gzip, deflate]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Cache-Control]符合预期: 预期[max-age=259200]== 实际[max-age=259200]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Host]符合预期: 预期[httpbin.org]== 实际[httpbin.org]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[User-Agent]符合预期: 预期[python-requests/2.27.1]== 实际[python-requests/2.27.1]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:93 - 字段[X-Amzn-Trace-Id]断言成功:预期[Root=1-62734553-430db0707e1a3656043cd165]== 实际[Root=\d-\w{8}-\w{24}]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[headers]符合预期: 预期[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62734553-430db0707e1a3656043cd165'}]== 实际[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=\\d-\\w{8}-\\w{24}'}]
2022-05-05 19:39:36.953 | WARNING  | __main__:assert_json_body:77 - 字段【origin】使用通用字段的正则匹配, 符合预期
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[age]符合预期: 预期[18]== 实际[18]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[name]符合预期: 预期[he]== 实际[he]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[req_param]符合预期: 预期[[{'age': '18', 'local_class': 'HttpBin', 'name': 'he'}]]== 实际[[{'name': 'he', 'age': '18'}]]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[url]符合预期: 预期[http://httpbin.org/set?name=bingo]== 实际[http://httpbin.org/set?name=bingo]

summary

  • It can be used as an independent function, or it can be packaged with all interfaces, with high flexibility and reusability
  • Use cases are minimal and logical
  • Support assertion failure, use case continues to execute (switch control), convenient to find all differences at one time
  • Extremely easy to write unified assertions ( easily fetched from logs )
    • Use case running log, the assertion data field is clear and clear
    • New version modification fields can be processed uniformly without modifying each use case
    • Support regular matching
    • Support for **contains matching**
    • Support error code direct assertion
  • Centrally initialize the original common data, easy to adapt to different businesses, just rewrite the initialization method

Advanced

Although the above method uses the log method to record all the differences, it is difficult to directly mark the specific difference position when faced with large json comparisons. Such a requirement arose when doing a comparison test of live network drainage. There may be hundreds of sub-accounts in the account data pulled from the live network, and each sub-account has more than 20 attribute fields. How to accurately mark their writing in the old and new systems The inconsistency after the operation becomes a small stuck point.

Not much to say, idea: use the variable feature of the list and the yield feature of the generator keyword to decompose json recursively, generate a fixed array, and finally compare the data in the array

code:

def recurse(d, prefix=None, sep='.'):
    if prefix is None:
        prefix = []
    for key, value in d.items():
        if isinstance(value, dict):
            yield from recurse(value, prefix + [key])
        elif isinstance(value, list):

            for i, v in enumerate(value):
                if isinstance(v, dict):
                    yield from recurse(v, prefix + [key, f"${i}"])

                # 兼容 包含数字的类型
                elif isinstance(v, int) or isinstance(v, str):
                    yield sep.join(prefix + [key, str(value)])  # 会嵌套上value
        else:
            # print(key)
            yield sep.join(prefix + [key, str(value)])  # 会嵌套上value

Effect:

print(json.dumps(list(recurse({
            "args": {
                "name": "bingo"
            },
            "headers": {
                "Accept": "*/*",
                "Accept-Encoding": "gzip, deflate",
                "Cache-Control": "max-age=259200",
                "Host": "httpbin.org",
                "User-Agent": "python-requests/2.27.1",
                "X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"
            },
            "req_param": [
                {
                    "name": "bingo",
                    "age": "18"
                },
                {
                    "name": "he",
                    "age": "19"
                },
                {
                    "name": "detector",
                    "age": "20"
                }
            ],
            "origin": r"",
            "url": "http://httpbin.org/set?name=bingo"
        })), indent=4))
# 输出
[
    "args.name.bingo",
    "headers.Accept.*/*",
    "headers.Accept-Encoding.gzip, deflate",
    "headers.Cache-Control.max-age=259200",
    "headers.Host.httpbin.org",
    "headers.User-Agent.python-requests/2.27.1",
    "headers.X-Amzn-Trace-Id.Root=\\d-\\w{8}-\\w{24}",
    "req_param.$0.name.bingo",
    "req_param.$0.age.18",
    "req_param.$1.name.he",
    "req_param.$1.age.19",
    "req_param.$2.name.detector",
    "req_param.$2.age.20",
    "origin.",
    "url.http://httpbin.org/set?name=bingo"
]

Summarize

In project practice, there will always be such and such requirements. Each method has applicable scenarios, and solving problems directly and efficiently is the top priority.

  • json lookup data
  • Encapsulation comparison of json data use cases
  • JSON data conversion

 Data acquisition method

【Message 777】

Friends who want to get source code and other tutorial materials, please like + comment + bookmark , triple!

After three times in a row , I will send you private messages one by one in the comment area~

Guess you like

Origin blog.csdn.net/GDYY3721/article/details/132164842