Test Discovery and Collection

This module implements the mechanisms by which pytest locates and collects Python test files, classes, functions, and methods based on user-configured naming conventions and patterns. It builds a hierarchical collection tree representing the test suite, enabling pytest to later execute and report on these tests.


Core Concepts and Purpose

This module solves the problem of flexible, extensible, and accurate identification of tests in arbitrary Python codebases, respecting naming conventions, user configuration, and test organization patterns.


Key Functionalities and Workflows

1. File and Directory Collection

2. Module Collection

3. Class and Function Collection

4. Parametrization During Collection

5. Test Identification Filters

6. Setup and Teardown Integration


Interaction with Other System Components


Important Concepts and Design Patterns


Illustrative Code References

Directory Collection Decision

def pytest_collect_directory(path: Path, parent: nodes.Collector) -> nodes.Collector | None:
    pkginit = path / "__init__.py"
    try:
        has_pkginit = pkginit.is_file()
    except PermissionError:
        return None
    if has_pkginit:
        return Package.from_parent(parent, path=path)
    return None

This logic decides whether a directory is a package collector or not.

File Collection Filtering

def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> nodes.Module | None:
    if file_path.suffix == ".py":
        if not parent.session.isinitpath(file_path):
            if not path_matches_patterns(file_path, parent.config.getini("python_files")):
                return None
        ihook = parent.session.gethookproxy(file_path)
        module = ihook.pytest_pycollect_makemodule(module_path=file_path, parent=parent)
        return module
    return None

Files not matching configured test file patterns are ignored.

Class Filtering for Test Classes

def istestclass(self, obj: object, name: str) -> bool:
    if not (self.classnamefilter(name) or self.isnosetest(obj)):
        return False
    if inspect.isabstract(obj):
        return False
    return True

Classes are filtered by name and other checks before being collected.

Function Filtering for Test Functions

def istestfunction(self, obj: object, name: str) -> bool:
    if self.funcnamefilter(name) or self.isnosetest(obj):
        if isinstance(obj, (staticmethod, classmethod)):
            obj = safe_getattr(obj, "__func__", False)
        return callable(obj) and fixtures.getfixturemarker(obj) is None
    else:
        return False

Only functions matching naming conventions and not marked as fixtures are collected as tests.

Parametrization Processing

def pytest_generate_tests(metafunc: Metafunc) -> None:
    for marker in metafunc.definition.iter_markers(name="parametrize"):
        metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)

Hooks allow `@pytest.mark.parametrize` to generate multiple test invocations at collection time.


Mermaid Diagram: Test Discovery and Collection Workflow

flowchart TD
    Start[Start Test Discovery]
    DirCheck{Is Directory?}
    HasInitPy[Contains __init__.py?]
    PackageCollector[Create Package Collector]
    PlainDirCollector[Create Plain Directory Collector]
    FileCheck{Is Python File (*.py)?}
    MatchesFilePattern{Matches python_files pattern?}
    ModuleCollector[Create Module Collector]
    ImportModule[Import Module]
    CollectClasses[Collect Test Classes]
    CollectFunctions[Collect Test Functions]
    Parametrize[Process Parametrization]
    CollectSetupTeardown[Register Setup/Teardown Fixtures]
    End[Collection Complete]

    Start --> DirCheck
    DirCheck -- Yes --> HasInitPy
    HasInitPy -- Yes --> PackageCollector --> PackageCollector --> End
    HasInitPy -- No --> PlainDirCollector --> End
    DirCheck -- No --> FileCheck
    FileCheck -- Yes --> MatchesFilePattern
    MatchesFilePattern -- Yes --> ModuleCollector --> ImportModule --> CollectClasses --> CollectFunctions --> Parametrize --> CollectSetupTeardown --> End
    MatchesFilePattern -- No --> End
    FileCheck -- No --> End

This flowchart depicts how pytest discovers and collects tests starting from the filesystem, through directories and files, importing modules, and collecting classes and functions, including handling parametrization and setup registration.


Summary

The **Test Discovery and Collection** module is responsible for translating a user’s test directory and file structure into an internal collection tree of test nodes. It applies naming conventions and user configurations to identify test modules, classes, and functions, supports parametrized tests, and integrates setup/teardown methods as fixtures. This process lays the foundation for pytest's test execution and reporting capabilities.