pytest自定义断言,如何获得追溯

时间:2017-12-01 13:09:15

标签: python pytest

我得到了一个简单的测试代码:

test_assert.py

import helper


def test_assert():
    assert helper.find_event(helper.HelperEvents("event","this is info")) is True

helper.py

helper_list = []


class HelperEvents:
    def __init__(self, event, info):
        self.event = event
        self.info = info


def add_event(event):
    helper_list.append(event)


def find_event(event):
    try:
        helper_list.index(event, 0, 100)
    except ValueError:
        return False
    return True

这会给我一个像这样的AssertionError:

    def test_assert():
>       assert helper.find_event(helper.HelperEvents("event","this is info")) is True
E       AssertionError: assert False is True
E        +  where False = <function find_event at 0x158c9b0>(<test_asserts.helper.HelperEvents instance at 0x157d488>)
E        +    where <function find_event at 0x158c9b0> = helper.find_event
E        +    and   <test_asserts.helper.HelperEvents instance at 0x157d488> = <class test_asserts.helper.HelperEvents at 0x1670188>('event', 'this is info')
E        +      where <class test_asserts.helper.HelperEvents at 0x1670188> = helper.HelperEvents

test_assert.py:5: AssertionError

但我真的想简化这样的事情: AssertionError:事件:事件信息:这是在列表中找不到的信息

为此,我制作了一个 init .py文件

import pytest
pytest.register_assert_rewrite('test_asserts')

和conftest.py文件:

import pytest


@pytest.hookimpl(tryfirst=True)
def pytest_assertrepr_compare(op, left, right):
    print "Left:", left
    print "op:", op
    print "Right:", right
    return [str(left), op, str(right)]

这给了我以下内容:

    def test_assert():
>       assert helper.find_event(helper.HelperEvents("event","this is info")) is True
E       assert False
E         is
E         True

test_assert.py:5: AssertionError

当我只看到find_event调用的结果时,如何从事件中提取信息?

1 个答案:

答案 0 :(得分:0)

所以我最终为pytest_runtest_makereport实现了一个钩子。 我重用pytests实现中的代码并使用我自己的自定义断言表示扩展它。 它看起来像这样:

import pytest
from _pytest._code.code import ExceptionInfo
from _pytest.outcomes import skip
from _pytest._code.code import ReprTraceback, ReprEntry, ReprFileLocation, Traceback
from _pytest.runner import TestReport


@pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call):
    when = call.when
    duration = call.stop - call.start
    keywords = dict([(x, 1) for x in item.keywords])
    excinfo = call.excinfo
    sections = []
    if not call.excinfo:
        outcome = "passed"
        longrepr = None
    else:
        if not isinstance(excinfo, ExceptionInfo):
            outcome = "failed"
            longrepr = excinfo
        elif excinfo.errisinstance(skip.Exception):
            outcome = "skipped"
            r = excinfo._getreprcrash()
            longrepr = (str(r.path), r.lineno, r.message)
        else:
            outcome = "failed"
            if call.when == "call":
                longrepr = custom_excinfo_repr(excinfo) # This creates my custom assert representation
            else:  # exception in setup or teardown
                longrepr = item._repr_failure_py(excinfo,
                                                 style=item.config.option.tbstyle)
    for rwhen, key, content in item._report_sections:
        sections.append(("Captured %s %s" % (key, rwhen), content))
    if longr


def beautify_exc_output(lines):
    out = []
    if any("helper.find_event" in s for s in lines):
        out = parseAssert(lines)
    return out


def parseAssertion(lines):
    assertionType = ""
    args = []
    out = []
    compare = []

    # extract data from input
    for line in lines:
        line = line.strip("+ ")
        if "AssertionError" in line:
            assertionType = line[line.find("assert"):]
            compare = [int(s) for s in assertionType.split() if s.isdigit()]  # Check if assertion was an integer compare
        if "HelperEvents" in line and "where" not in line:
            content = line[line.find("(")+1:line.find(")")]  # Find content of event
            args = [x.strip(" '") for x in content.split(",")]  # split in to args

    # Generate output
    if len(args) > 1 and len(assertionType) > 0:
        Indent = "    "
        out.append("Assertion error in HelperEvents:")
        out.append("###############################################################")
        if len(compare) == 2:
            out.append("  Expected {0} times {1} but got {2} times".format(compare[1], args[0], compare[0]))
        out.append("  " + GetAssertionEvent(assertionType))
        out.append(Indent + "Event: {}".format(args[0]))
        out.append(Indent + "Info: {}".format(args[1]))
        out.append("###############################################################")
    return out


def GetAssertionEvent(AssertionType):
    if "assert not False" in AssertionType or \
            "assert True is False" in AssertionType or \
            "assert True" in AssertionType:
        return "Unexpected event has been received"
    elif "assert False" in AssertionType or \
            "assert False is True" in AssertionType:
        return "Expected event was not received"
    else:
        return AssertionType

现在所有这些代码都放在conftest.py中。 如果断言是一种通过实现无法识别的类型,它将从我的钩子返回None,这将使pytest使用默认实现。