Typorithm · medium

Capture stdout and stderr deliberately

pythonpytesttestingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Capture stdout and stderr deliberately

0%

Concept and contract

Assert user-visible reports without conflating output and return values.

A command-style helper can have two public results: a return code and text written to a stream. Returning zero does not prove that the message is correct, and printing a successful-looking message does not prove that the return code is zero. Test these responsibilities explicitly.

stdout normally carries ordinary output, while stderr carries diagnostics. This convention should be documented by the command. Do not merge the streams before checking them if a caller or pipeline relies on their distinction.

Mechanism and worked example

The complete example prints a greeting to stdout and returns a success code.

The complete example prints a greeting to stdout and returns a success code. capsys.readouterr gives the captured output so far and resets the capture buffer. The test asserts the return code, exact newline-terminated stdout and empty stderr.

A later read observes output since the previous read, not the full session repeatedly. If a test uses capsys.disabled, output during that block bypasses capture intentionally. Keep such blocks for interactive debugging rather than using them to avoid an assertion.

Complete worked example

python

def greet(name):
    print(f"Hello, {name}")
    return 0


def test_greeting_output(capsys):
    assert greet("Ada") == 0
    captured = capsys.readouterr()
    assert captured.out == "Hello, Ada\n"
    assert captured.err == ""
Reasoning
The complete example prints a greeting to stdout and returns a success code. capsys.readouterr gives the captured output so far and resets the capture buffer. The test asserts the return code, exact newline-terminated stdout and empty stderr. A later read observes output since the previous read, not the full session repeatedly. If a test uses capsys.disabled, output during that block bypasses capture intentionally. Keep such blocks for interactive debugging rather than using them to avoid an assertion.
Open in local Python playground ↗

Focused test decision: context is in the complete example

python

captured = capsys.readouterr()
assert captured.out == "Hello, Ada\n"
assert captured.err == ""
Focused test decision
Type this short test decision after understanding the complete runnable module. Desktop typing records practice; Run tests in the local playground executes real pytest.

Counterexamples and common mistakes

The controlled function prints the wrong greeting.

The controlled function prints the wrong greeting. The exact output test fails despite the zero return code. A weak assertion such as assert captured.out would only establish that some text was printed. Substring checks can likewise miss duplicated or unexpectedly formatted output.

capsys captures Python-level stream behavior; capfd addresses file descriptors in environments that support them. The browser’s worker output is not a complete operating-system terminal. Do not claim that a capsys example verifies native subprocess or descriptor behavior.

Controlled bug: the tests must report failure

python

def greet(name):
    print(f"Goodbye, {name}")
    return 0


def test_greeting_output(capsys):
    assert greet("Ada") == 0
    captured = capsys.readouterr()
    assert captured.out == "Hello, Ada\n"
    assert captured.err == ""
Reasoning
The controlled function prints the wrong greeting. The exact output test fails despite the zero return code. A weak assertion such as assert captured.out would only establish that some text was printed. Substring checks can likewise miss duplicated or unexpectedly formatted output. capsys captures Python-level stream behavior; capfd addresses file descriptors in environments that support them. The browser’s worker output is not a complete operating-system terminal. Do not claim that a capsys example verifies native subprocess or descriptor behavior.
Open in local Python playground ↗

Independent practice

Add a second greeting call and verify that each read receives only the new message.

Add a second greeting call and verify that each read receives only the new message. This makes the capture-reset behavior visible. Retain the original single-message test and its explicit stderr assertion.

Type the readouterr and assertion lines after understanding where the print occurs. Predict the exact newline rather than trimming it away automatically. If the UI contract intentionally ignores trailing whitespace, document that policy before normalizing the expected text.

Step 1: Add a two-call test that checks capture resets between reads. Step 2: Keep the existing passing cases. Add the smallest new assertion first; predict the failure before running pytest.

Practice starter: solve before reading the solution

python

def greet(name):
    print(f"Hello, {name}")
    return 0


def test_greeting_output(capsys):
    assert greet("Ada") == 0
    captured = capsys.readouterr()
    assert captured.out == "Hello, Ada\n"
    assert captured.err == ""

# Task: Add a two-call test that checks capture resets between reads.
Reasoning
Add a two-call test that checks capture resets between reads.
Open in local Python playground ↗
  • Hint 1: Read after the Ada call, then read again after Lin.
  • Hint 2: The second expected output contains only Lin’s line.

Solution, tests and trade-offs

The solution performs two calls with a capture read between them.

The solution performs two calls with a capture read between them. Its probe duplicates output only when the later name is used. The original greeting still passes; the new read catches the extra line. This makes the new assertion sensitive to the described output regression.

Captured output may include diagnostics from setup or logging depending on the chosen fixture and configuration. If logs are the intended behavior, use caplog and assert records rather than treating every log as ordinary stdout.

Practice solution and acceptance checks

python

def greet(name):
    print(f"Hello, {name}")
    return 0


def test_greeting_output(capsys):
    assert greet("Ada") == 0
    captured = capsys.readouterr()
    assert captured.out == "Hello, Ada\n"
    assert captured.err == ""

def test_capture_resets_between_reads(capsys):
    greet("Ada")
    assert capsys.readouterr().out == "Hello, Ada\n"
    greet("Lin")
    assert capsys.readouterr().out == "Hello, Lin\n"
Reasoning
The solution performs two calls with a capture read between them. Its probe duplicates output only when the later name is used. The original greeting still passes; the new read catches the extra line. This makes the new assertion sensitive to the described output regression. Captured output may include diagnostics from setup or logging depending on the chosen fixture and configuration. If logs are the intended behavior, use caplog and assert records rather than treating every log as ordinary stdout.
Open in local Python playground ↗

Interview reasoning and next step

Explain the difference between a return value, stdout, stderr and a log record.

Explain the difference between a return value, stdout, stderr and a log record. Describe how readouterr resets the observed buffer and why exact newlines can matter to a CLI contract. If output contains nondeterministic paths, isolate that variable rather than weakening every assertion.

Next control environment values with monkeypatch. A useful test changes only the dependency it owns and relies on automatic restoration instead of leaving process-wide settings behind.

Optional stretch — after the core task works: Introduce one deliberate implementation mistake and explain which assertion catches it. A green run alone is not evidence that every possible bug is covered.

Computer lab: https://applydjinn.com/typorithm/pytest-lab-20261106.zip. Run verify.py in an isolated environment. Worked examples and solutions pass, controlled counterexamples fail, and the new practice assertions reject a mutation missed by the baseline. Run --practice pyq-capsys after changing the supplied exercise. Python executes on your computer or in the browser, without submitting code to a server. Native question progress is a separate learning record.

Assert user-visible reports without conflating output and return values.