Python pytest自定义断言,如何获取回溯

Python pytest自定义断言,如何获取回溯,python,pytest,Python,Pytest,我得到了一个简单的测试代码,如下所示: 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>(<te

我得到了一个简单的测试代码,如下所示:

    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
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
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
这将给我一个断言错误,如下所示:

    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
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文件:

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\u事件调用的结果时,如何从事件中提取信息?

因此我最终实现了pytest\u runtest\u makereport的钩子。 我重用了pytests实现中的代码,并使用自己的自定义断言表示对其进行了扩展。 看起来是这样的:

    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
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使用默认实现