Tutorial

Once integrated in your project, the Pytest::Pytest target and the pytest_discover_tests() function are available for using.

Using the target

Let’s consider a project which wraps C++ logic with Python bindings. We need to add a CMakeLists.txt configuration file to add Python tests within the same directory. The Pytest command can be easily implemented using the add_test function:

add_test(
    NAME PythonTest
    COMMAND Pytest::Pytest ${CMAKE_CURRENT_SOURCE_DIR}
)

For the tests to run, the PYTHONPATH environment variable must be updated to locate the built package library. We can use an expression generator to resolve the path of the dependent target directory dynamically:

set_tests_properties(
    TEST PythonTest
    PROPERTY ENVIRONMENT
        PYTHONPATH=$<TARGET_FILE_DIR:MyLibrary>:$ENV{PYTHONPATH}
)

The shared library might also be required during runtime execution, so its location should be provided:

set_tests_properties(
    TEST PythonTest
    APPEND PROPERTY ENVIRONMENT
        LD_LIBRARY_PATH=$<TARGET_FILE_DIR:MyLibrary>:$ENV{LD_LIBRARY_PATH}
)

Warning

The environment variable used to locate shared libraries depends on the platform. LD_LIBRARY_PATH is used on Linux, DYLD_LIBRARY_PATH on macOS, and PATH on Windows.

After building the project, the command can then be executed by CTest. If all tests are successful, the output will look as follows:

    Start 1: PythonTest
1/1 Test #1: PythonTest .......................   Passed    0.55 sec

However, if only one test is unsuccessful, the entire test suite will be marked as failed.

    Start 1: PythonTest
1/1 Test #1: PythonTest .......................***Failed    0.47 sec

Using the function

A pytest_discover_tests() function is provided to create CTest tests for each Python test collected. Therefore, the configuration added in the previous section could be replaced by the following:

pytest_discover_tests(
    PythonTest
    LIBRARY_PATH_PREPEND
        $<TARGET_FILE_DIR:MyLibrary>
    PYTHON_PATH_PREPEND
        $<TARGET_FILE_DIR:MyLibrary>
    TRIM_FROM_NAME "^test_"
    DEPENDS MyLibrary
)

This will create a new PythonTest target, dependent on the MyLibrary target.

The expected environment can be defined simply with the LIBRARY_PATH_PREPEND and PYTHON_PATH_PREPEND arguments, which both accept multiple values. The environment variable used to locate shared libraries will be automatically chosen according to the platform.

Pytest usually requires tests to start with a specific prefix, which can be trimmed using the TRIM_FROM_NAME argument. The value can use a regular expression to match the part of the test name that should be trimmed.

A list of dependent targets can be defined with the DEPENDS argument, which accepts multiple values.

After building the project, running CTest will display the tests as follows:

    Start 1: PythonTest.greet_world
1/4 Test #1: PythonTest.greet_world ...........   Passed    0.47 sec
    Start 2: PythonTest.greet_john
2/4 Test #2: PythonTest.greet_john ............   Passed    0.47 sec
    Start 3: PythonTest.greet_julia
3/4 Test #3: PythonTest.greet_julia ...........   Passed    0.47 sec
    Start 4: PythonTest.greet_michael
4/4 Test #4: PythonTest.greet_michael .........   Passed    0.54 sec

It is also possible to regroup all tests under one CTest test, as was the case when using the target. This can be useful during development to ensure that the tests run faster, especially if you use fixtures with a broader scope.

This can be done by setting the BUNDLE_TESTS argument to True:

 pytest_discover_tests(
     PythonTest
     LIBRARY_PATH_PREPEND
         $<TARGET_FILE_DIR:MyLibrary>
     PYTHON_PATH_PREPEND
         $<TARGET_FILE_DIR:MyLibrary>
     TRIM_FROM_NAME "^test_"
     DEPENDS MyLibrary
     BUNDLE_TESTS True
 )

After re-building the project, running CTest will display the tests as follows:

    Start 1: PythonTest
1/1 Test #1: PythonTest .......................   Passed    0.51 sec

Note

The BUNDLE_PYTHON_TESTS environment variable can also set this argument dynamically.