pythonpytest

Selectively calling test functions with capsys


I can selectively run test_function_1, overriding instrumentation from my conftest.py fixtures1

def test_function_1(instrumentation: dict[str, float]) -> None:
    assert instrumentation['a'] > instrumentation['b']

def test_function_2(capsys) -> None:
    print("Hello, pytest!")
    captured = capsys.readouterr()
    assert captured.out == "Hello, pytest!\n"

when I try to call test_function_2, I don't know how to pass capsys to it2 :

import tests
import pytest # <--- doesn't help ...

def test_callee_with_instrumentation():
    tests.test_function_1({'a': 110, 'b': 55, 'g': 6000})

def test_callee_with_capsys():
    # tests.test_function_2()              # <--- TypeError: test_function_2() missing 1 required positional argument: 'capsys'
    # tests.test_function_2(capsys)        # <--- NameError: name 'capsys' is not defined 
    # tests.test_function_2(pytest.capsys) # <--- AttributeError: module 'pytest' has no attribute 'capsys'
    pass

test_callee_with_instrumentation()
test_callee_with_capsys()

I'm pretty sure the conftest.py fixtures are irrelevant, but for completeness:

import pytest

@pytest.fixture(scope='function')
def instrumentation():
    return { 'a': 800, 'b': 620, 'c': 44 }

1 In my real code, the capsys is one of many parameters.

2 A similar question exists here. It is not a duplicate IMHO, because I'm asking about programmatically running tests, and not about the proper meaning of capsys.


Solution

  • capsys is a PyTest fixture.

    You can list all the fixtures using:

    pytest --fixtures
    

    The source code is found in _pytest/capture.py. (Note: You should not try to directly instantiate/call the fixture.)

    Write your function with an argument:

    import tests
    
    def test_callee_with_instrumentation():
        tests.test_function_1({'a': 110, 'b': 55, 'g': 6000})
    
    def test_callee_with_capsys(capsys):
        tests.test_function_2(capsys)
    

    Then run pytest your_test_file.py and PyTest will automatically instantiate the fixture and pass it into the test.


    If you don't want to use PyTest to run the tests then don't use capsys to try to capture the standard output.

    from contextlib import redirect_stdout
    from io import StringIO
    
    def test_function_1(instrumentation) -> None:
        assert instrumentation['a'] > instrumentation['b']
    
    def test_function_2() -> None:
        with redirect_stdout(StringIO()) as f:
            print("Hello, pytest!")
        captured = f.getvalue()
        assert captured == "Hello, pytest!\n"
    
    

    If you do want to use PyTest but don't want to directly call PyTest from the console then call pytest.main() from within your code:

    import tests
    
    def test_callee_with_instrumentation():
        tests.test_function_1({'a': 110, 'b': 55, 'g': 6000})
    
    def test_callee_with_output(capsys):
        tests.test_function_2(capsys)
    
    if __name__ == "__main__":
        import pytest
        pytest.main()