Python Test Collectors

Purpose

Python Test Collectors address the need to systematically discover and organize Python test components—modules, packages, classes, and functions—within the pytest framework. This subtopic focuses on the mechanisms that identify which Python files and objects represent tests based on naming patterns, decorators, and attributes, enabling pytest to build a structured collection tree that reflects the test suite hierarchy.

While the parent topic introduces test discovery and collection broadly, Python Test Collectors specifically implement the logic for recognizing Python test artifacts and creating corresponding collector and item nodes. They solve the problem of filtering relevant test units from arbitrary Python code and mapping them into pytest's internal collection nodes for subsequent execution.

Functionality

Python Test Collectors operate through a layered collection process, moving from filesystem-level entities down to individual test functions:

Key Workflow Example

  1. File Collection:
    pytest_collect_file checks if a Python file matches configured patterns and creates a Module collector.

  2. Module Collection:
    The Module collector imports the module and registers any legacy module-level setup/teardown via autouse fixtures.

  3. Within Module - Classes and Functions:
    The collect method scans the module's dictionary and base classes' dictionaries, filtering names and objects. For each, pytest_pycollect_makeitem is called, which returns either a Class collector (for test classes) or Function items (for test functions).

  4. Class Collection:
    The Class collector similarly registers class-level and method-level setup/teardown fixtures, then collects its test methods (functions) as Function items.

  5. Function Collection:
    Function items represent individual test calls, potentially parametrized by Metafunc.

Relationship to Parent Topic and Other Subtopics

Python Test Collectors implement the core logic of test discovery and collection specifically for Python code, complementing the broader **Test Discovery and Collection** mechanism. While the parent topic covers general mechanisms and hooks, this subtopic provides the concrete classes and methods that perform detailed introspection of Python test modules, classes, and functions.

It integrates closely with:

The **Custom Directory Collection** subtopic complements this by handling directory-level collection logic, whereas Python Test Collectors handle the Python-specific content inside files and packages.

Illustrative Code Snippets

File Collection and Module Creation

def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> nodes.Collector | 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
        module = parent.session.gethookproxy(file_path).pytest_pycollect_makemodule(
            module_path=file_path, parent=parent
        )
        return module
    return None

def pytest_pycollect_makemodule(module_path: Path, parent) -> Module:
    return Module.from_parent(parent, path=module_path)

Collecting Test Classes and Functions within a Module

class PyCollector(nodes.Node, abc.ABC):
    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
        dicts = [getattr(self.obj, "__dict__", {})]
        if isinstance(self.obj, type):
            for basecls in self.obj.__mro__:
                dicts.append(basecls.__dict__)
        seen = set()
        result = []
        for dic in reversed(dicts):
            for name, obj in dic.items():
                if name in seen or name in IGNORED_ATTRIBUTES:
                    continue
                seen.add(name)
                res = self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj)
                if res is None:
                    continue
                elif isinstance(res, list):
                    result.extend(res)
                else:
                    result.append(res)
        return result

Identifying 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

Diagram

flowchart TD
    A[Filesystem Entry] -->|Is directory with __init__.py| B[Package Collector]
    A -->|Is Python file matching patterns| C[Module Collector]
    B --> D[Collect subdirectories and files]
    C --> E[Import Module]
    E --> F{Inspect Objects in Module}
    F -->|Is Test Class| G[Class Collector]
    F -->|Is Test Function| H[Function Item]
    G --> I{Inspect Methods}
    I -->|Is Test Method| H
    H --> J[Parameterized Test Instances]

This flowchart visualizes how Python Test Collectors traverse the filesystem, identify packages and modules, then collect test classes and functions, eventually producing test items ready for execution.


This explanation highlights the purpose and detailed mechanisms of Python Test Collectors, clarifying their role in pytest’s test discovery and their collaboration with related system parts.