python pytest capsys:检查输出并得到报告?

mu0hgdu0  于 2023-05-21  发布在  Python
关注(0)|答案(5)|浏览(213)

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 fixture时,就像这样:

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文档中对readouterr的描述是:“测试功能完成后,将恢复原始流。”
我试着假设capsys是一个上下文管理器,并在Assert之前调用了capsys.__exit__()。这将是一个丑陋的解决方案,但至少是一个解决方案,如果它恢复了我的Assert之前的输出。然而,这只产生

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

接下来,我查看了CaptureFixture类的源代码,发现了一个看起来很有希望的方法close(它调用了一些pop_outerr_to_orig()方法),但是在我的测试中调用capsys.close()也没有帮助,它根本没有明显的效果。
如何让pytest在使用capsys的测试失败时报告我的输出?

gab6jxml

gab6jxml1#

您看到了正确的行为,当使用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
r6l8ljro

r6l8ljro2#

从文档来看,行为似乎是正确的:只有在test函数(test_result_and_stdout)完成后,输出流才会恢复,而不是在每次readouterr调用后。我不认为目前capsys除了捕获它们之外还支持重定向到原始流,这似乎是你想要的。
我建议在官方仓库中使用create an issue,看看人们有什么要说的。

1qczuiv0

1qczuiv03#

正如@flub所描述的,当使用capsys时,stdout和stderr的输出会被捕获和使用,这是capsys的预期行为。如果您想在测试错误捕获中看到消息,可以在捕获消息后将它们写回stdout和stderr。
然而,我发现一个常见的模式是作为同一测试的一部分连续运行几个命令,通常是因为一个命令后的状态更改会影响下一个命令的行为。在测试详细输出时,您需要针对测试中特定命令的输出内容进行测试,而不是测试期间运行的整个输出历史。为了隔离单个命令的输出,您需要在调用命令之前和之后捕获stdout/stderr。这样,在为一个独立的命令打印到stdout之前,您可以 * 捕获并丢弃 * 以前的stdout内容。
不幸的是,在捕获消息后将其发送回stdout和stderr并不合适,因为如果您这样做,它们将污染测试中下一个命令的输出。
我的解决方案是创建一个辅助函数recapsys,它的作用是捕获并立即返回stdout/stderr的内容(就像@flub描述的那样)。此外,recapsys接受任意多个以前的捕获作为参数,并在当前捕获之前重新生成这些捕获。这样,您就可以在调用测试命令之前捕获以前的stdout/stderr内容,然后在运行命令之后将它们打印出来。
下面给出了recapsys的定义和示例用法。(对于recapsys的基于类的实现,请查看我的Python模板存储库中的类方法。)

# 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

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

===================================== 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 =================================
r9f1avp5

r9f1avp54#

使用“startswith”作为旁白。您也可以使用“in”关键字,例如:

assert "Hello" in output

如果你有大量的数据被传递到stdout,这是很好的,你可以使用“in”来检查你的stdout中的不同行。

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

你也可以Assert传入stderr而不是stdout的内容:

assert "What you are expecting" in err

还请注意这一行:

out, err = capsys.readouterr()

创建到目前为止stdout和stderr的输出的快照,以便您可以Assert该测试所期望的内容。

ih99xse1

ih99xse15#

在pytest >= 3中,capsyscapsysfd都有disabled()方法,可以用来暂时禁用捕获,使之能够输出诊断。(除了更干净,这是唯一对我有效的方法。

相关问题