pytest8.x版本 中文使用文档-------12.如何捕获标准输出(stdout)/标准错误输出(stderr)

目录

默认的stdout/stderr/stdin捕获行为

设置捕获方法或禁用捕获

使用print 打印语句进行调试

从测试函数中访问捕获的输出


默认的stdout/stderr/stdin捕获行为

在测试执行期间,发送到stdout和stderr的任何输出都会被捕获。如果一个测试或设置方法失败,那么相应的捕获输出通常会与失败跟踪信息一起显示(此行为可以通过--show-capture命令行选项进行配置)。

这意味着,在默认情况下,pytest会捕获并保存你的测试代码(包括测试函数、测试类中的设置和清理方法等)中产生的所有标准输出(stdout)和标准错误输出(stderr),以便在测试失败时提供有用的调试信息。如果你想要查看或处理这些捕获的输出,你可以使用pytest的捕获机制提供的功能,比如caplog(对于日志捕获)或capsys(对于stdout/stderr捕获)。

此外,stdin 被设置为一个“空”对象,尝试从中读取时会失败,因为在运行自动化测试时很少需要等待交互式输入。

默认情况下,捕获是通过拦截对低级文件描述符的写入来完成的。这允许捕获来自简单打印语句的输出,以及由测试启动的子进程的输出。

设置捕获方法或禁用捕获

在使用 pytest 进行测试时,可以通过设置不同的捕获方法来控制标准输出(stdout)和标准错误输出(stderr)的捕获方式,或者选择完全禁用捕获功能。

pytest 提供了三种捕获输出的方式:

  1. 文件描述符(fd)级别捕获(默认):所有写入到操作系统文件描述符 1(标准输出 stdout)和 2(标准错误 stderr)的内容都将被捕获。这种方式下,pytest 会拦截并保存这些输出,以便后续分析或报告使用。

  2. 系统(sys)级别捕获:仅捕获写入到 Python 标准库中的 sys.stdout 和 sys.stderr 的内容。与文件描述符级别捕获不同,这种方式不会捕获直接通过文件描述符进行的写入操作。这种方式可能更适合那些严格通过 Python 标准库进行输出的场景。

  3. tee-sys 捕获:在这种模式下,Python 写入到 sys.stdout 和 sys.stderr 的内容不仅会被捕获,同时还会被传递到实际的 sys.stdout 和 sys.stderr。这允许输出既能“实时打印”到控制台,又能被捕获以供插件使用,如 junitxml(pytest 5.4 中新增)。这对于需要同时查看实时输出和生成测试报告的场景非常有用。

你可以通过命令行来影响输出捕获机制:

pytest -s                  # 禁用所有捕获  
pytest --capture=sys       # 将 sys.stdout/stderr 替换为内存中的文件,捕获输出但不显示在终端  
pytest --capture=fd        # 同时也将文件描述符 1 和 2 指向临时文件,捕获标准输出和标准错误  
pytest --capture=tee-sys   # 结合 'sys' 和 '-s' 的功能,捕获 sys.stdout/stderr 的内容  
                           # 并将这些内容传递给实际的 sys.stdout/stderr,以便同时捕获和实时显示输出

通过这些命令行选项,你可以控制 pytest 在测试运行期间如何捕获和显示输出。-s 选项完全禁用了捕获,使得所有输出都会直接显示在终端上。--capture=sys 选项则只捕获通过 Python 标准库 sys.stdout 和 sys.stderr 进行的输出,但不会显示在终端上。--capture=fd 选项更进一步,它不仅捕获通过 Python 标准库的输出,还捕获了所有通过文件描述符 1(标准输出)和 2(标准错误)的输出,即使这些输出并非来自 Python 代码。最后,--capture=tee-sys 选项结合了捕获和显示的功能,使得输出既被捕获以便后续分析,又能够实时显示在终端上。

使用print 打印语句进行调试

stdout/stderr 输出默认捕获的一个主要好处是,你可以使用打印语句进行调试:

# content of test_module.py


def setup_function(function):
    print("setting up", function)


def test_func1():
    assert True


def test_func2():
    assert False

运行这个模块将精确地显示失败函数的输出,并隐藏另一个:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

test_module.py .F                                                    [100%]

================================= FAILURES =================================
________________________________ test_func2 ________________________________

    def test_func2():
>       assert False
E       assert False

test_module.py:12: AssertionError
-------------------------- Captured stdout setup ---------------------------
setting up <function test_func2 at 0xdeadbeef0001>
========================= short test summary info ==========================
FAILED test_module.py::test_func2 - assert False
======================= 1 failed, 1 passed in 0.12s ========================

对于失败的测试 test_func2,pytest 提供了详细的失败信息,包括失败的具体行、失败原因(AssertionError)以及任何捕获的输出(在这个例子中是 setup_function 的输出,但请注意,这不是通过 pytest 的标准机制捕获的,可能是通过其他方式或假设的)。

从测试函数中访问捕获的输出

capsyscapsysbinarycapfd 和 capfdbinary 这些夹具(fixtures)允许你在测试执行期间访问 stdout/stderr 的输出。下面是一个示例测试函数,它执行一些与输出相关的检查:

def test_myoutput(capsys):  # or use "capfd" for fd-level
    print("hello")
    sys.stderr.write("world\n")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
    assert captured.err == "world\n"
    print("next")
    captured = capsys.readouterr()
    assert captured.out == "next\n"

test_myoutput 函数使用 capsys 夹具来捕获测试执行过程中的输出。该函数首先向 stdout 打印 "hello",然后向 stderr 写入 "world\n"(注意,在 stderr 上写入时,通常需要换行符 \n 以确保输出被正确识别为单独的一行)。

通过调用 capsys.readouterr(),我们可以捕获到目前为止所有输出到 stdout 和 stderr 的内容。readouterr() 方法返回一个包含两个属性的命名元组(named tuple):out 和 err,分别对应 stdout 和 stderr 的捕获内容。

在第一个断言中,我们检查 captured.out 是否等于 "hello\n",以确保 "hello" 及其后的换行符被正确捕获并输出到 stdout。同样,第二个断言检查 captured.err 是否等于 "world\n",以确保 "world\n" 被正确捕获并输出到 stderr。

然后,函数再次向 stdout 打印 "next",并再次调用 capsys.readouterr() 来捕获新的输出。此时,我们断言 captured.out 是否等于 "next\n",以验证 "next" 及其后的换行符也被正确捕获。

需要注意的是,一旦 readouterr() 被调用,它就会清除之前的捕获内容,因此第二次调用 readouterr() 时只会捕获到 "next\n" 这一行输出。

readouterr() 调用会捕获到目前为止的输出快照——并且捕获操作会继续进行。测试函数执行完毕后,原始流将被恢复。以这种方式使用 capsys 可以让你的测试无需关心设置/重置输出流的问题,同时也与 pytest 自身的每测试捕获机制配合良好。

具体来说,readouterr() 方法会在调用时捕获从测试开始到调用该方法时为止的所有输出(包括 stdout 和 stderr),但它不会停止捕获过程。在测试函数的后续执行中,如果有更多的输出产生,这些输出将继续被捕获,直到测试函数结束。然而,readouterr() 方法每次调用时返回的是到该调用时刻为止的捕获内容的快照,之后的输出不会包含在内。

当测试函数执行完毕后,pytest 会自动恢复原始的 stdout 和 stderr 流,这意味着测试不会对后续测试的执行产生副作用。

如果你想要在文件描述符级别上进行捕获,你可以使用 capfd 夹具(fixture),它提供了与 capsys 相同的接口,但还允许捕获来自库或子进程的输出,这些库或子进程直接写入操作系统级别的输出流(FD1 和 FD2)。

FD1 通常代表标准输出(stdout),而 FD2 代表标准错误输出(stderr)。使用 capfd 时,你可以像使用 capsys 一样调用 readouterr() 方法来获取捕获的输出,但捕获的范围更广,包括了那些直接写入 FD1 和 FD2 的输出。这使得 capfd 成为了一种更强大、更灵活的捕获机制,特别适用于需要深入测试系统级交互的场景。

如果被测试的代码写入非文本数据,你可以使用 capsysbinary 夹具来捕获这些数据。与 capsys 不同,capsysbinary 的 readouterr 方法返回的是字节(bytes)而不是字符串(str)。

同样地,如果被测试的代码写入非文本数据,并且你希望在文件描述符级别进行捕获,那么你可以使用 capfdbinary 夹具。capfdbinary 夹具也是通过 readouterr 方法返回字节数据,但它是在文件描述符级别上进行操作的。

这两种夹具(capsysbinary 和 capfdbinary)特别适用于捕获二进制数据或包含非ASCII字符的数据。由于它们返回的是字节数据,因此你可以对捕获到的数据进行更详细的二进制分析或处理。这对于测试那些生成图像、视频、音频文件或其他二进制格式数据的程序特别有用。

为了在测试内部临时禁用捕获功能,capsys 和 capfd 都提供了一个 disabled() 方法,该方法可以用作上下文管理器(context manager),在 with 代码块内禁用捕获功能:

def test_disabling_capturing(capsys):  
    print("this output is captured")  # 这行输出会被捕获  
    with capsys.disabled():  
        print("output not captured, going directly to sys.stdout")  # 这行输出不会被捕获,而是直接输出到 sys.stdout  
    print("this output is also captured")  # 这行输出同样会被捕获

当 capsys.disabled() 被用作上下文管理器时,with 代码块内的所有输出都将不会被 capsys 捕获,而是直接发送到 Python 的标准输出流 sys.stdout。这允许你在测试过程中临时禁用捕获功能,以便执行一些需要直接查看输出或向用户展示输出结果的操作。然而,需要注意的是,这种方法并不会影响 pytest 自身的捕获机制,它只影响 capsys 或 capfd 夹具的捕获行为。

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值