如何在py.test终结器中打印东西

时间:2015-06-26 17:10:49

标签: python unit-testing pytest

我正在测试一个写入日志文件的函数(具体是它写入日志文件并不重要,它可能正在做任何事情,它只是引起这个问题的原因)

这样的事情:

def do_stuff():
    with open('/tmp/mylogs.txt', 'a') as f:
        f.write(str(time.time()))
        f.write(' stuff done! \n')
    return 42

我可以测试一下这个:

def test_doing_stuff(watch_logs):
    assert do_stuff() == 42
    assert do_stuff() == 43

出于调试目的,当测试失败时,我希望能够打印出新的日志记录 - 我可以像这样制作一个夹具:

@pytest.fixture()
def watch_logs(request):
    with open('/tmp/mylogs.txt') as f:
        log_before = f.read()

    def get_new_logs():
        with open('/tmp/mylogs.txt') as f:
            log_after = f.read()
            return log_after.replace(log_before, '')

    return get_new_logs

太棒了 - 现在我可以在测试中的任何一点检查日志内容:

def test_doing_stuff(watch_logs):
    assert do_stuff() == 42
    print(watch_logs())
    assert do_stuff() == 43
    print(watch_logs())
嗯 - 啊,但第二次打印不起作用,它是在测试失败后。

如果我的测试夹具总是在测试结束时打印出日志怎么办?然后pytest的stdout捕获会在它失败时显示给我,但不会在它通过时显示它!

@pytest.fixture()
def watch_logs(request):
    with open('/tmp/mylogs.txt') as f:
        log_before = f.read()

    def get_new_logs():
        with open('/tmp/mylogs.txt') as f:
            log_after = f.read()
            return log_after.replace(log_before, '')

    def print_new_logs():
        print('~' * 20 + ' logs ' + '~' * 20)
        print(get_new_logs())
        print('~' * 50)
    request.addfinalizer(print_new_logs)

    return get_new_logs

哦,但是这不起作用,因为在测试终结器期间没有发生pytests的日志捕获。

所以问题是:如何制作可以打印东西的测试终结器?

这是一个超级最小的要点,没有(无关的)写入日志文件的东西:https://gist.github.com/hjwp/5154ec40a476a5c01ba6

2 个答案:

答案 0 :(得分:3)

没有记录或干净的方法来实现它,但这是一个黑客:

# conftest.py

def pytest_runtest_call(item):
    if hasattr(item, "_request"):
        if hasattr(item._request, "_addoutput_on_failure"):
            item._request._addoutput_on_failure()

# test_x.py
import pytest

@pytest.fixture
def print_on_fail(request):
    def add():
        print ("test failed")
    request._parent_request._addoutput_on_failure = add

def test_doing_stuff(print_on_fail):
    assert False

我们可以考虑一个合适的request.addcall_on_failure(callback) API。

使yield_fixture案例工作需要一些内部可能非平凡的重构。

答案 1 :(得分:2)

感谢Holger自己的帮助(感谢@ hpk42!),我有一些有用的东西。只有轻微的魔力/ hacky。

解决方案是使用名为pytest_pyfunc_call的py.test挂钩,以及名为hookwrapper的装饰器。它们为我提供了一种方法,可以在测试运行之前和之后挂钩一些代码,但也不会受到stdout劫持的影响。

我们在 conftest.py 中定义了一个新函数:

# conftest.py

@pytest.mark.hookwrapper
def pytest_pyfunc_call(pyfuncitem):
    yield
    print('this happens after the test runs')
    if 'watch_logs' in pyfuncitem.funcargs:
        print(pyfuncitem.funcargs['watch_logs']())

现在,如果pytest发现任何使用watch_logs夹具的测试,它将在测试运行后打印其输出。

完整示例:https://gist.github.com/hjwp/4294f0acbef5345a7d46