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:
Module and Package Collection:
Python files (*.py) are identified as test modules if they match glob patterns defined in thepython_filesconfiguration (e.g.,"test_*.py"). Directories containing an__init__.pyfile are treated as packages and collected accordingly. The pytest_collect_directory andpytest_collect_filehooks manage these steps.Class and Function Collection:
Within modules, classes and functions are inspected to determine if they qualify as test classes or test functions.Classes are recognized as test classes if their names match patterns from python_classes (default prefix
"Test"), are not abstract, and do not define__init__or new constructors that would interfere with pytest instantiation.Functions are identified as test functions if their names match python_functions patterns (default prefix
"test"), are callable, not marked as fixtures, and not asynchronous (which require plugins).The system also respects the test attribute to include or exclude objects explicitly.
Parametrization and Generation of Test Items:
Test functions can be parameterized, resulting in multiple test invocations from one function definition. The Metafunc class manages parameter sets and generates unique test IDs for each variant. The collectors yieldFunctionitem nodes accordingly.XUnit-Style Setup/Teardown Support:
To integrate with legacy setup/teardown patterns, collectors register autouse fixtures that invokesetup_module,teardown_module,setup_class,teardown_class,setup_method, and teardown_method if defined (and not marked as fixtures). This ensures backward compatibility and smooth fixture interaction.Hook Integration and Customization:
Pytest hooks likepytest_pycollect_makeitemandpytest_pycollect_makemoduleallow plugins to customize collection behavior. The collectors call these hooks to create nodes, enabling extensibility.
Key Workflow Example
File Collection:
pytest_collect_filechecks if a Python file matches configured patterns and creates aModulecollector.Module Collection:
TheModulecollector imports the module and registers any legacy module-level setup/teardown via autouse fixtures.Within Module - Classes and Functions:
Thecollectmethod scans the module's dictionary and base classes' dictionaries, filtering names and objects. For each,pytest_pycollect_makeitemis called, which returns either aClasscollector (for test classes) orFunctionitems (for test functions).Class Collection:
TheClasscollector similarly registers class-level and method-level setup/teardown fixtures, then collects its test methods (functions) asFunctionitems.Function Collection:
Functionitems 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:
Fixture Management: Collectors register autouse fixtures for legacy setup/teardown integration.
Plugin System and Hooks: Collection behavior is customizable via hooks that the collectors invoke.
Test Execution and Reporting: Collected
Functionitems form the execution units.Test Skipping and Expected Failures: Item nodes created by collectors carry marker information influencing execution.
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.