从pytest断言中解析出数据并转换成字符串

Parsing out data from pytest assert and converting into a string

我想将有关测试的数据收集到外部数据库,我将在其中收集所有断言并解析预期值与实际结果。

在代码中描述我想要的东西:

test_something.py:

def test_me_1(send_to_test):
    """
    Docstring to pad even more
    """
    x,y = send_to_test(3)
    assert x == 3**2
    assert y == 3**3
    assert y == 3**2 # I want this to fail intentionally


conftest.py:

@pytest.fixture(scope='function')
def send_to_test() -> Callable:
    return lambda x: (x ** 2, x ** 3)



我要收集的数据是(其他任何方式都可以,只要我得到这个数据):

test_data = [{'func': 'test_me_1', 'variable': 'x', 'expected_value': 9, 'actual_value': 9},
             {'func': 'test_me_1', 'variable': 'y', 'expected_value': 27, 'actual_value': 27},
             {'func': 'test_me_1', 'variable': 'y', 'expected_value': 27, 'actual_value': 9}]

我目前的想法是一个包装器,它可以很容易地实现到现有的测试中,并将收集断言语句及其值。
如果我使用 AST,收集 assert 语句的数量很简单,但这些仅适用于静态分析。 我也尝试过使用 inspect 模块,但很难弄清楚如何为此目的使用它

到目前为止我的失败尝试(打印语句是临时的,因为我想收集数据)

def get_this(func: Callable):
    @functools.wraps(func)
    def wrapper(*arg, **kwargs):
        print(func.__name__)
        func_content = inspect.getsource(func)
        func_root = ast.parse(func_content)
        for node in func_root.body:
            for content in node.body:
                if isinstance(content, ast.Assert):
                    print(content)
        return func(*arg, **kwargs)

    return wrapper

你可以试试这个:

import inspect
import json
from typing import Callable

import pytest


@pytest.fixture(scope="function")
def send_to_test() -> Callable:
    return lambda x: (x ** 2, x ** 3)

# Helper function to run and monitor a test
def run_test(test_func, variable, expected_value, actual_value, data):
    try:
        with open("./data.json", "r") as f:
            data = json.load(f)
    except FileNotFoundError:
        data = []

    data.append(
        {
            "func": test_func,
            "variable": variable,
            "expected_value": expected_value,
            "actual_value": actual_value,
        }
    )
    with open("./data.json", "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)

    assert actual_value == expected_value


# Reformatted tests
def test_me_1(send_to_test):
    # setup
    data = []
    x, y = send_to_test(3)

    # tests
    run_test(
        test_func=inspect.stack()[0][3],
        variable="x",
        expected_value=3 ** 2,
        actual_value=x,
        data=data,
    )
    run_test(
        test_func=inspect.stack()[0][3],
        variable="y",
        expected_value=3 ** 3,
        actual_value=y,
        data=data,
    )
    run_test(
        test_func=inspect.stack()[0][3],
        variable="y",
        expected_value=3 ** 2,
        actual_value=y,
        data=data,
    )

因此,当您 运行 pytest 时,会创建一个包含预期内容的新 data.json 文件:

[
    {
        "func": "test_me_1",
        "variable": "x",
        "expected_value": 9,
        "actual_value": 9
    },
    {
        "func": "test_me_1",
        "variable": "y",
        "expected_value": 27,
        "actual_value": 27
    },
    {
        "func": "test_me_1",
        "variable": "y",
        "expected_value": 9,
        "actual_value": 27
    }
]

@hoefling 通过在 conftest 中使用以下挂钩的非常简单的解决方案回答了我的问题:

def pytest_assertrepr_compare(op, left, right):...
def pytest_assertion_pass(item, lineno, orig, expl):...

根据 Pytest 文档,我必须添加到 pytest.ini enable_assertion_pass_hook=true 并删除 .pyc 文件,但除此之外它的工作原理非常棒。 现在我已经对使用的运算符进行了左右比较(pytest_assertrepr_compare),如果测试通过了(pytest_assertion_pass)。