对于写入标准输出或标准错误输出的测试,自动判定为失败
使用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时成功结束。