对于写入标准输出或标准错误输出的测试,自动判定为失败

编程语言 2026-07-12

使用pytest,如果测试体在执行过程中向标准输出(stdout)或标准错误(stderr)写入任何内容,我就能让一个单独的单元测试失败,像下面这样:

import pytest
def test_no_stray_output(capsys: pytest.CaptureFixture[str]) -> None:

    # actual test logic here

    out, err = capsys.readouterr()
    assert out == ""
    assert err == ""

我想找一种方法,能够对测试套件中的所有测试自动执行这一步,或等价的实现。(这是在测试一个库,这个库本来就不应触及标准流,但它的维护相当混乱,我一直发现深在内部的已提交调试打印语句;同时,pytest对 stdout和 stderr的默认处理会掩盖这些问题。)

理想情况下,我也希望让对sys.stdin的所有读取,以及对操作系统级别的文件描述符0 的读取抛出异常,但这并不是特别重要。

解决方案

Fixtures可以 请求其他fixtures,因此你可以创建一个 yield fixture,它会成为一个 自动使用的fixtures(你无需显式请求),例如在根级 conftest.py,从而确保对 capsys 的检查在相关作用域内的每个测试中都会应用:

from collections.abc import Iterator

import pytest


@pytest.fixture(autouse=True)
def _fail_on_stray_output(capsys: pytest.CaptureFixture[str]) -> Iterator[None]:
    yield
    out, err = capsys.readouterr()
    assert out == ""
    assert err == ""

stdin的默认捕获行为 已经是对任意读取报错:

...stdin 被设置为一个“空对象”,因为在运行自动化测试时通常不希望等待交互式输入,所以尝试读取它时会失败。

因此,给定一个包含如下内容的测试文件,例如:

def test_passes_no_input_or_output():
    pass


def test_fails_on_print():
    print("oh no!")  # noqa: T201


def test_fails_on_input():
    input("")

你将看到类似如下的输出:

$ uv run pytest
=========================================== test session starts ============================================
platform darwin -- Python 3.11.11, pytest-9.0.2, pluggy-1.6.0
rootdir: path/to/project
configfile: pyproject.toml
collected 3 items                                                                                          

tests/test_outputs.py ..EF                                                                           [100%]

================================================== ERRORS ==================================================
_________________________________ ERROR at teardown of test_fails_on_print _________________________________

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

    @pytest.fixture(autouse=True)
    def _fail_on_stray_output(capsys: pytest.CaptureFixture[str]) -> Iterator[None]:
        yield
        out, err = capsys.readouterr()
>       assert out == ""
E       AssertionError: assert 'oh no!\n' == ''
E         
E         + oh no!

tests/conftest.py:10: AssertionError
------------------------------------------- Captured stdout call -------------------------------------------
oh no!
================================================= FAILURES =================================================
___________________________________________ test_fails_on_input ____________________________________________

    def test_fails_on_input():
>       input("")

tests/test_outputs.py:10: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.capture.DontReadFromInput object at 0x1073758d0>, size = -1

    def read(self, size: int = -1) -> str:
>       raise OSError(
            "pytest: reading from stdin while output is captured!  Consider using `-s`."
        )
E       OSError: pytest: reading from stdin while output is captured!  Consider using `-s`.

.venv/lib/python3.11/site-packages/_pytest/capture.py:229: OSError
========================================= short test summary info ==========================================
FAILED tests/test_outputs.py::test_fails_on_input - OSError: pytest: reading from stdin while output is captured!  Consider using `-s`.
ERROR tests/test_outputs.py::test_fails_on_print - AssertionError: assert 'oh no!\n' == ''
=================================== 1 failed, 2 passed, 1 error in 0.05s ===================================

:产生输出的测试在技术上实际上是通过的,但在teardown时会报错。如果测试在读取输入时有非空的提示符,因为它也是输出的一部分,那么它将失败并在teardown时出错。

此外还值得注意的是,测试仍然可以自行请求 capsys fixture,而且根据 readouterr 将会(此处强调):

读取并返回到目前为止捕获的输出,同时重置内部缓冲区。

对于像 def test_succeeds_expected_output(capsys: pytest.CaptureFixture[str]): print("passing") # noqa: T201 out, _ = capsys.readouterr() assert out == "passing\n" 这样的“预期写入”测试,将会通过并在teardown时成功结束。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章