pythonpytest

How to import submodules under tests/ and still run only tests in subdirectory in Python


I have a the following test folder layout:

tests
├── __init__.py
├── conftest.py
├── data_validation
│   └── test_data_validation.py
└── per_vendor
    ├── __init__.py
    ├── vendor_a
    │   ├── conftest.py
    │   └── test_vendor_a.py
    └── vendor_b
        ├── __init__.py
        ├── conftest.py
        └── test_vendor_b.py

In tests/conftest.py I do:

import pytest

from tests import per_vendor

@pytest.fixture
def database_with_data(empty_database: Engine) -> Engine:
    helpers = [
        per_vendor.vendor_a.conftest.fill_with_data_for_a,
        per_vendor.vendor_b.conftest.fill_with_data_for_b,
    ]
    for cur in helpers:
        cur(empty_database)
    return empty_database

Here, empty_database is another fixture providing an empty database for my tests.

This setup works if I run all tests: python -m pytest tests/. However, if I only want to run tests in a specific subfolder, e.g. python -m pytest tests/data_validation/, all tests relying on the fixture database_with_data fail due to an error: AttributeError: module 'tests.per_vendor' has no attribute 'vendor_a'.

Note that tests.per_vendor still can be imported. However, the submodules there are not picked up.

I'd like to understand better why in one case the submodules are picked up and in the other they aren't. Also, if possible, I'd like to find a solution that allows the modular setup and running tests in subfolders.


Solution

  • When you give the pytest command a directory, it walks the directory tree importing packages and modules, looking for test modules to run. This means that when you do python -m pytest tests, pytest will automatically have imported tests.per_vendor.vendor_a for you, before it runs any of your tests or fixtures. However, when you do python -m pytest tests/data_validation, this search for importable packages and modules is restricted to tests/data_validation and tests/per_vendor is not searched and none of its sub-packages or sub-modules are imported. Thus, without pytest walking tests/per_vendor, the module tests.per_vendor will not have the attribute vendor_a.

    It is important to note that when python imports a package, it does not automatically import sub-modules and sub-packages (unless the package __init__.py has code to do so). What this means in practice is that when your fixture needs tests.per_vendor.vendor_a.conftest it should explicitly import that module and not just tests.per_vendor. Thus the imports in your code sample might become:

    import pytest
    
    from tests import per_vendor
    assert not hasattr(per_vendor, 'vendor_a')  # sub-packages not yet imported
    
    import tests.per_vendor.vendor_a.conftest
    import tests.per_vendor.vendor_b.conftest
    assert hasattr(per_vendor, 'vendor_a')