How to capture stdout/stderr output
Default stdout/stderr/stdin capturing behaviour
During test execution any output sent to stdout
and stderr
is captured. If a test or a setup method fails its according captured output will usually be shown along with the failure traceback. (this behavior can be configured by the --show-capture
command-line option).
In addition, stdin
is set to a “null” object which will fail on attempts to read from it because it is rarely desired to wait for interactive input when running automated tests.
By default capturing is done by intercepting writes to low level file descriptors. This allows to capture output from simple print statements as well as output from a subprocess started by a test.
Setting capturing methods or disabling capturing
There are three ways in which pytest
can perform capturing:
fd
(file descriptor) level capturing (default): All writes going to the operating system file descriptors 1 and 2 will be captured.sys level capturing: Only writes to Python files
sys.stdout
andsys.stderr
will be captured. No capturing of writes to filedescriptors is performed.tee-sys
capturing: Python writes tosys.stdout
andsys.stderr
will be captured, however the writes will also be passed-through to the actualsys.stdout
andsys.stderr
. This allows output to be ‘live printed’ and captured for plugin use, such as junitxml (new in pytest 5.4).
You can influence output capturing mechanisms from the command line:
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
pytest --capture=tee-sys # combines 'sys' and '-s', capturing sys.stdout/stderr
# and passing it along to the actual sys.stdout/stderr
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
pytest --capture=tee-sys # combines 'sys' and '-s', capturing sys.stdout/stderr
# and passing it along to the actual sys.stdout/stderr
Using print statements for debugging
One primary benefit of the default capturing of stdout/stderr output is that you can use print statements for debugging:
# content of test_module.py
def setup_function(function):
print("setting up", function)
def test_func1():
assert True
def test_func2():
assert False
# content of test_module.py
def setup_function(function):
print("setting up", function)
def test_func1():
assert True
def test_func2():
assert False
and running this module will show you precisely the output of the failing function and hide the other one:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.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 ========================
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.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 ========================
Accessing captured output from a test function
The capsys
, capsysbinary
, capfd
, and capfdbinary
fixtures allow access to stdout/stderr output created during test execution. Here is an example test function that performs some output related checks:
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"
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"
The readouterr()
call snapshots the output so far - and capturing will be continued. After the test function finishes the original streams will be restored. Using capsys
this way frees your test from having to care about setting/resetting output streams and also interacts well with pytest's own per-test capturing.
If you want to capture on filedescriptor level you can use the capfd
fixture which offers the exact same interface but allows to also capture output from libraries or subprocesses that directly write to operating system level output streams (FD1 and FD2).
The return value from readouterr
changed to a namedtuple
with two attributes, out
and err
.
If the code under test writes non-textual data, you can capture this using the capsysbinary
fixture which instead returns bytes
from the readouterr
method.
If the code under test writes non-textual data, you can capture this using the capfdbinary
fixture which instead returns bytes
from the readouterr
method. The capfdbinary
fixture operates on the filedescriptor level.
To temporarily disable capture within a test, both capsys
and capfd
have a disabled()
method that can be used as a context manager, disabling capture inside the with
block:
def test_disabling_capturing(capsys):
print("this output is captured")
with capsys.disabled():
print("output not captured, going directly to sys.stdout")
print("this output is also captured")
def test_disabling_capturing(capsys):
print("this output is captured")
with capsys.disabled():
print("output not captured, going directly to sys.stdout")
print("this output is also captured")