pytest capsys:检查输出并报告?

时间:2014-10-25 11:23:41

标签: python pytest

Python 3.4.1,pytest 2.6.2。

当测试失败时,pytest将定期报告测试打印到stdout的内容。 例如这段代码:

def method_under_test():
    print("Hallo, Welt!")
    return 41

def test_result_only():
    result = method_under_test()
    assert result == 42

作为python -m pytest myfile.py执行时, 将报告此事:

================================== FAILURES ===================================
______________________________ test_result_only _______________________________

    def test_result_only():
        result = method_under_test()
>       assert result == 42
E       assert 41 == 42

pytestest.py:9: AssertionError
---------------------------- Captured stdout call -----------------------------
Hallo, Welt!
========================== 1 failed in 0.03 seconds ===========================

这是一个非常好的功能。 但是当我使用pytest的内置capsys夹具时,就像这样:

def test_result_and_stdout(capsys):
    result = method_under_test()
    out, err = capsys.readouterr()
    assert out.startswith("Hello")
    assert result == 42

报告不再包含实际输出:

================================== FAILURES ===================================
___________________________ test_result_and_stdout ____________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000000000331FB70>

    def test_result_and_stdout(capsys):
        result = method_under_test()
        out, err = capsys.readouterr()
>       assert out.startswith("Hello")
E       assert <built-in method startswith of str object at 0x000000000330C3B0>('Hello')
E        +  where <built-in method startswith of str object at 0x000000000330C3B0> = 'Hallo, Welt!\n'.startswith

pytestest.py:14: AssertionError
========================== 1 failed in 0.03 seconds ===========================

我不确定这种行为是否符合规范; pytest documentationreadouterr: “测试功能完成后 原始流将被恢复。“

我已经尝试假设capsys是一个上下文管理器而且有 在断言之前调用capsys.__exit__()。 这将是一个丑陋的解决方案,但至少是一个解决方案 它在断言之前恢复了输出。 但是,这只会产生

AttributeError: 'CaptureFixture' object has no attribute '__exit__'

接下来,我查看了CaptureFixture类源代码 并找到了一个看起来很有前途的方法close(调用 一些pop_outerr_to_orig()方法), 但在我的测试中调用capsys.close()也无济于事, 它没有明显的效果。

如何在失败时让pytest报告我的输出 在使用capsys

的测试中

4 个答案:

答案 0 :(得分:11)

当您使用capsys.readouterr()消耗捕获的输出时,您会看到正确的行为。因此,stdout和stderr的任何输出都将不再显示在测试报告中。但是,在此之后创建并且不消耗的任何新输出仍将被报告,因此您可以通过简单地将其再次写入输出流来获得报告中的完整输出:

def test_result_and_stdout(capsys):
    result = method_under_test()
    out, err = capsys.readouterr()
    sys.stdout.write(out)
    sys.stderr.write(err)
    assert out.startswith("Hello")
    assert result == 42

答案 1 :(得分:5)

从文档中看,行为似乎是正确的:只有在测试函数(test_result_and_stdout)完成后,才会恢复输出流,而不是在每次readouterr调用之后。我不认为目前的capsys支持除了捕获它们之外还会重定向到原始流,这似乎是你想要的。

我建议在官方存储库中create an issue查看人们要说的内容。

答案 2 :(得分:1)

除了使用“startswith”之外。您还可以使用“in”关键字,例如:

    assert "Hello" in output

如果您将大量数据传递给stdout,这很棒,您可以使用“in”来检查标准输出中的不同行。

def test_result_and_stdout(capsys):
    result = method_under_test()
    out, err = capsys.readouterr()
    sys.stdout.write(out)
    sys.stderr.write(err)
    assert "Hello" in out 
    assert result == 42

您还可以使用:

断言传递给stderr而不是stdout的内容
    assert "What you are expecting" in err

另请注意:

    out, err = capsys.readouterr()
到目前为止,

创建了stdout和stderr输出的快照,因此您可以断言您对该测试的期望。

答案 3 :(得分:0)

正如@flub 所描述的,当使用 capsys 时,输出到 stdout 和 stderr 会被捕获和消耗,这是 capsys 的预期行为。如果您想在测试错误捕获中查看消息,可以在捕获消息后将消息写回 stdout 和 stderr。

但是,我发现一个常见的模式是在同一个测试中连续运行几个命令,通常是因为在一个命令影响下一个命令后的状态发生变化。在测试详细输出时,您需要针对测试中特定命令的输出内容进行测试,而不是测试期间运行的输出的整个历史记录。为了将输出与单个命令隔离,您需要在调用命令之前和之后捕获 stdout/stderr。这样你就捕获并丢弃先前的标准输出内容,然后再打印到标准输出以获取隔离命令。

不幸的是,这并不适合在捕获消息后将消息发送回 stdout 和 stderr,因为如果这样做,它们会污染测试中下一个命令的输出。

我的解决方案是创建一个辅助函数 recapsys,它的作用是捕获并立即反刍 stdout/stderr 的内容(就像 @flub 描述的那样)。此外,recapsys 接受任意多个先前的捕获作为参数,并将在当前捕获之前反刍这些。有了这个,你可以在调用被测命令之前捕获以前的stdout/stderr内容,然后在运行命令后打印出来。

recapsys 的定义和用法示例如下。 (有关 recapsys 的基于类的实现,请查看 class methods in my python template repository。)

# Contents of test_fn.py

import sys


def recapsys(capsys, *captures):
    capture_now = capsys.readouterr()
    for capture in captures + (capture_now,):
        sys.stdout.write(capture.out)
        sys.stderr.write(capture.err)
    return capture_now


def test_stdout(capsys):
    capture_pre = capsys.readouterr()  # Clear stdout
    msg = "To be, or not to be, that is the question:"
    print(msg)  # Execute method (verbose)
    capture_post = recapsys(capsys, capture_pre)  # Capture and then re-output
    assert capture_post.out == msg + "\n"  # Compare output to target

    capture_pre = capsys.readouterr()  # Clear stdout
    msg = "Whether 'tis nobler in the mind to suffer"
    print(msg)  # Execute method (verbose)
    capture_post = recapsys(capsys, capture_pre)  # Capture and then re-output
    assert capture_post.out.lower().startswith("whether")  # Test

    capture_pre = capsys.readouterr()  # Clear stdout
    msg = "The slings and arrows of outrageous fortune,"
    print(msg)  # Execute method (verbose)
    capture_post = recapsys(capsys, capture_pre)  # Capture and then re-output
    assert capture_post.out == "We're no strangers to love\n"  # Test

pytest 上运行 test_fn.py 会产生以下输出:

===================================== FAILURES ======================================
____________________________________ test_stdout ____________________________________

capsys = <_pytest.capture.CaptureFixture object at 0x7f49015ce278>

    def test_stdout(capsys):
        capture_pre = capsys.readouterr()  # Clear stdout
        msg = "To be, or not to be, that is the question:"
        print(msg)  # Execute method (verbose)
        capture_post = recapsys(capsys, capture_pre)  # Capture and then re-output
        assert capture_post.out == msg + "\n"  # Compare output to target
    
        capture_pre = capsys.readouterr()  # Clear stdout
        msg = "Whether 'tis nobler in the mind to suffer"
        print(msg)  # Execute method (verbose)
        capture_post = recapsys(capsys, capture_pre)  # Capture and then re-output
        assert capture_post.out.lower().startswith("whether")  # Test
    
        capture_pre = capsys.readouterr()  # Clear stdout
        msg = "The slings and arrows of outrageous fortune,"
        print(msg)  # Execute method (verbose)
        capture_post = recapsys(capsys, capture_pre)  # Capture and then re-output
>       assert capture_post.out == "We're no strangers to love\n"  # Test
E       assert 'The slings a...us fortune,\n' == "We're no strangers to love\n"
E         - We're no strangers to love
E         + The slings and arrows of outrageous fortune,

test_fn.py:30: AssertionError
------------------------------- Captured stdout call --------------------------------
To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
============================== short test summary info ==============================
FAILED test_fn.py::test_stdout - assert 'The slings a...us fortune,\n' == "We're n...
================================= 1 failed in 0.26s =================================