如何通过检查传递给pytest_runtest_teardown的Item对象来确定测试是通过还是失败?

时间:2014-06-20 01:44:44

标签: python pytest

Pytest允许您通过在插件中实现名为pytest_runtest_teardown的函数来挂钩每个测试的拆解阶段:

def pytest_runtest_teardown(item, nextitem):
    pass

我可以使用item上的属性或方法来确定刚刚运行的测试是通过还是失败?我找不到pytest.Item的任何文档,并且搜索源代码并在ipdb中玩游戏并没有发现任何明显的内容。

4 个答案:

答案 0 :(得分:4)

您也可以考虑pytest_runtest_makereport中的call.excinfo:

def pytest_runtest_makereport(item, call):
    if call.when == 'setup':
        print('Called after setup for test case is executed.')
    if call.when == 'call':
        print('Called after test case is executed.')
        print('-->{}<--'.format(call.excinfo)) 
    if call.when == 'teardown':
        print('Called after teardown for test case is executed.')

调用对象包含大量附加信息(测试开始时间,停止时间等)。

参见: http://doc.pytest.org/en/latest/_modules/_pytest/runner.html

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(pytest.skip.Exception):
            outcome = "skipped"
            r = excinfo._getreprcrash()
            longrepr = (str(r.path), r.lineno, r.message)
        else:
            outcome = "failed"
            if call.when == "call":
                longrepr = item.repr_failure(excinfo)
            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))
    return TestReport(item.nodeid, item.location,
                      keywords, outcome, longrepr, when,
                      sections, duration)

答案 1 :(得分:1)

Node class没有关于上一次测试状态的任何信息,但我们确实有失败测试总数的状态(item.session.testsfailed),我们可以使用它:

  1. 我们可以在item.session对象中添加一个新成员(不太好,但你必须爱上python!)。该成员将保存上一个testsfailed - item.session.last_testsfailed_status
  2. 的状态
  3. 如果testsfailed&gt; last_testsfailed_status - 运行失败的最后一次测试。
  4. import pytest
    import logging
    
    logging.basicConfig(
        level='INFO',
        handlers=(
            logging.StreamHandler(),
            logging.FileHandler('log.txt')
        )
    )
    
    @pytest.mark.hookwrapper
    def pytest_runtest_teardown(item, nextitem):
        outcome = yield
    
        if not hasattr(item.session, 'last_testsfailed_status'):
            item.session.last_testsfailed_status = 0
    
        if item.session.testsfailed and item.session.testsfailed > item.session.last_testsfailed_status:
            logging.info('Last test failed')
    
        item.session.last_testsfailed_status = item.session.testsfailed
    

答案 2 :(得分:0)

最初,我也很努力地获取测试状态,以便可以使用它来创建自定义报告。
但是,在进一步分析了pytest_runtest_makereport挂钩函数之后,我能够看到3个参数的各种属性(项目,调用和报告)。 让我只列出其中一些:

  1. 致电:
    • excinfo(这将进一步细化以进行追溯(如果有的话)
    • 开始(测试时间的开始时间,从时间开始算起)
    • 停止(测试的停止时间,从开始到开始,以浮动值表示)
    • 何时(可以获取值-设置,调用,拆卸)
  2. 项目:
    • _fixtureinfo(包含您使用过的所有灯具的信息)
    • nodeid(pytest假定的test_name)
    • cls(包含测试的类信息,info是指在测试类中声明和访问的变量)
    • funcargs(传递给测试的参数及其值)
  3. 报告:
    • 结果(带有测试状态)
    • longrepr(包含失败信息,包括回溯)
    • 何时(可以接受值-设置,调用,拆卸。请注意,报告将根据其值携带值)

仅供参考:我在上文中已经提到了上述3个参数的其他属性。 下面是一些代码片段,它们描述了我如何钩住函数和如何使用它。

def pytest_runtest_makereport(item, call, __multicall__):
report = __multicall__.execute()

if (call.when == "call") and hasattr(item, '_failed_expect'):
    report.outcome = "failed"
    summary = 'Failed Expectations:%s' % len(item._failed_expect)
    item._failed_expect.append(summary)
    report.longrepr = str(report.longrepr) + '\n' + ('\n'.join(item._failed_expect))

if call.when == "call":
    ExTest.name = item.nodeid
    func_args = item.funcargs
    ExTest.parameters_used = dict((k, v) for k, v in func_args.items() if v and not hasattr(v, '__dict__'))
    # [(k, v) for k, v in func_args.items() if v and not hasattr(v, '__dict__')]
    t = datetime.fromtimestamp(call.start)
    ExTest.start_timestamp = t.strftime('%Y-%m-%d::%I:%M:%S %p')
    ExTest.test_status = report.outcome
    # TODO Get traceback info (call.excinfo.traceback)  
    return report

答案 3 :(得分:0)

使用挂钩包装器-允许所有默认的挂钩运行,然后查看其结果。

下面的示例显示了两种检测测试是否失败的方法(将其添加到您的conftest.py

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    # Because this is a hookwrapper, calling `yield` lets the actual hooks run & returns a `_Result`
    result = yield
    # Get the actual `TestReport` which the hook(s) returned, having done the hard work for you
    report = result.get_result()

    # Method 1: `report.longrepr` is either None or a failure representation
    if report.longrepr:
        logging.error('FAILED: %s', report.longrepr)
    else:
        logging.info('Did not fail...')

    # Method 2: `report.outcome` is always one of ['passed', 'failed', 'skipped'] 
    if report.outcome == 'failed':
        logging.error('FAILED: %s', report.longrepr)
    elif report.outcome == 'skipped':
        logging.info('Skipped')
    else:  # report.outcome == 'passed'
        logging.info('Passed')

有关longreproutcome的详细信息,请参见TestReport文档

(它不使用pytest_runtest_teardown作为所请求的OP,但它很容易让您检查故障)