因为capsys只捕获sys.stdout,若输出走stderr、被缓冲未刷新、或由c/子进程直接写fd,则无法捕获;且readouterr()为消耗性操作,多次调用返回空。

pytest中用capsys捕获print()输出时为什么没抓到内容?
因为capsys默认只捕获sys.stdout,而某些场景下输出可能被缓冲、重定向,或实际走的是sys.stderr。常见表现是断言失败:assert "hello" in capsys.readouterr().out返回False,但控制台明明打印了。
- 确保被测函数确实调用
print()而非logging.info()或sys.stderr.write() - 如果函数内有
print(..., flush=True)或sys.stdout.flush(),一般不是问题;但若无flush且运行在非交互式环境(如CI),可能因缓冲延迟导致readouterr()取不到 -
capsys作用域仅限当前测试函数——不能跨def test_a():和def test_b():共享 - 避免在测试中手动调用
sys.stdout = StringIO(),会干扰capsys内部机制
什么时候必须用capfd而不是capsys?
当被测代码绕过Python层、直接写入底层文件描述符(比如用os.write(1, b"msg")、调用C扩展、或使用subprocess执行外部命令并打印到终端)时,capsys完全失效,必须换capfd。
-
capfd工作在文件描述符级别(fd=1对应stdout),能捕获所有写入该fd的字节流 - 代价是:捕获内容为
bytes,需显式解码:capfd.readouterr().out.decode("utf-8") - 性能略低,因涉及
os.dup2()等系统调用,普通纯Python输出无需强行用它 - 注意:Windows上
capfd对某些子进程输出的支持不稳定,优先在Linux/macOS验证
readouterr()调用一次后再次调用为什么返回空字符串?
readouterr()是“消耗性”操作——它读取并清空内部缓冲区。第二次调用时缓冲已空,自然返回空值。
- 正确做法:只调用一次,把结果存入变量再多次检查:
out, err = capsys.readouterr(),然后assert "x" in out、assert "y" not in err - 误用示例:
assert "a" in capsys.readouterr().out和assert "b" in capsys.readouterr().out—— 第二句永远失败 - 如果需要分阶段验证(比如先检查是否报错,再检查正常输出),应在同一
readouterr()结果里完成所有断言
在fixture或参数化测试中复用捕获逻辑要注意什么?
不能把capsys或capfd注入到自定义fixture中并返回,pytest不支持将这些fixture作为依赖传递给其他fixture——会触发ValueError: fixture 'capsys' not found。
- 正确方式:在每个需要捕获的测试函数签名中显式声明
capsys或capfd - 若逻辑重复多,可封装成普通函数接收
out和err字符串做断言,例如:def assert_output_contains(out: str, *patterns): ...,然后在各测试中调用assert_output_contains(capsys.readouterr().out, "OK", "done") - 参数化测试(
@pytest.mark.parametrize)中,每个参数组合都是独立测试用例,capsys自动为每个用例新建实例,无需额外处理
readouterr()的一次性特性——这两点导致的问题往往看起来像“pytest抽风”,其实只是机制没吃透。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











