DEV Community

Kelvin Wangonya
Kelvin Wangonya

Posted on • Originally published at wangonya.com

Capturing print statements while debugging

You might need to capture print statements in your tests while debugging. This might be just to help you debug, or for some other reason. I mostly do this kind of thing when testing for database connections where, in addition to other tests, I want to make sure I see that "Database connection successful" message.

Let's demonstrate with a simple example:

def add_numbers(num1, num2):
    print("Add function started...")
    result = num1 + num2
    print("Numbers added. Returning result...")
    return result

def test_add_numbers():
    assert add_numbers(2, 3) == 3
Enter fullscreen mode Exit fullscreen mode

Running the test above fails with the given traceback:

collected 1 item
print.py F                 [100%]

============================ FAILURES ============================

    def test_add_numbers():
>       assert add_numbers(2, 3) == 3
E       assert 5 == 3
E        +  where 5 = add_numbers(2, 3)

print.py:9: AssertionError
---------------------- Captured stdout call ----------------------------
Add function started...
Numbers added. Returning result...
===================== 1 failed in 0.11 seconds ====================
Enter fullscreen mode Exit fullscreen mode

Notice the Captured stdout call section. It captured out two print statements. During test execution, any output sent to stdout and stderr is captured. By default, the captured output is only displayed if the test fails. Changing our assert add_numbers(2, 3) == 3 to assert add_numbers(2, 3) == 5 and running the test again gives the following output:

collected 1 item
print.py .                       [100%]

========================= 1 passed in 0.05 seconds ===========================
Enter fullscreen mode Exit fullscreen mode

You can access your captured output in your tests by using readouterr():

def add_numbers(num1, num2):
    print("Add function started...")
    result = num1 + num2
    print("Numbers added. Returning result...")
    return result

def test_add_numbers(capsys):
    result = add_numbers(2, 3)
    captured = capsys.readouterr()
    assert "Hello" in captured.out
    assert result == 3
Enter fullscreen mode Exit fullscreen mode
collected 1 item
print.py F                             [100%]

====================== FAILURES =======================

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

    def test_add_numbers(capsys):
        result = add_numbers(2, 3)
        captured = capsys.readouterr()
>       assert "Hello" in captured.out
E       AssertionError: assert 'Hello' in 'Add function started...\nNumbers added. Returning result...\n'
E        +  where 'Add function started...\nNumbers added. Returning result...\n' = CaptureResult(out='Add function started...\nNumbers added. Returning result...\n', err='').out

print.py:11: AssertionError
======================== 1 failed in 0.09 seconds =======================
Enter fullscreen mode Exit fullscreen mode

It's important to note that each readouterr() call snapshots the output so far, so we can call it repeatedly, checking the output of our test step by step.

def add_numbers(num1, num2):
    print("Add function started...")
    result = num1 + num2
    print("Numbers added. Returning result...")
    return result

def test_add_numbers(capsys):
    result = add_numbers(2, 3)
    captured = capsys.readouterr()
    assert "Add function started" in captured.out
    assert "Numbers added. Returning result" in captured.out
    # thus far, readouterr() out only has the two print statements from the add_numbers function
    assert result == 5
    print("thank you, next")
    captured = capsys.readouterr()
    # here, readouterr() out doesn't have the two outputs from above
    # it now contains the new value, "thank you, next"
    assert captured.out == "thank you, next\n"
Enter fullscreen mode Exit fullscreen mode
collected 1 item
print.py .                       [100%]

========================= 1 passed in 0.05 seconds ===========================
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
simchuck profile image
simchuck

Is the capsys variable name a keyword for pytest, or can the name be arbitrary?

Also, can this technique also be used within a parametrized test? Where would the capsys parameter be placed in the test function's parameter list?

Collapse
 
victorcosta profile image
Victor Costa

Hey, @wangonya

Great tutorial series :D. Thanks for writing such a useful content.

I just would like to make a little suggestion regarding the assertion of the captured print output.

You're using

assert "<SOME_STRING>" in captured.out

The in statement verifies whether captured.out contains <SOME_STRING>, which means that if <SOME_STRING> is aa and the captured.out was aaaaaa the test would pass.

I would suggest to change that assertion to

assert "<SOME_STRING>\n" == captured.out

What do you say?