Test Lifecycle Management
Purpose
Test Lifecycle Management addresses the orchestration of running individual test items within pytest. This includes managing the sequence of phases each test undergoes—**setup**, **call** (execution), and **teardown**—as well as handling the outcomes of these phases (passing, failing, skipping, or erroring). It ensures that test resources are correctly initialized and cleaned up, errors are properly reported, and the test environment remains consistent throughout the test session.
This subtopic focuses on the precise control flow and state management required to execute tests reliably, which is essential for pytest’s robustness and user-friendly reporting but is not covered in the broader scope of Test Execution and Reporting.
Functionality
Core Workflow
Each test item follows a strict lifecycle with the phases:
Setup: Prepare the test environment, including fixtures and any necessary state.
Call: Execute the actual test function or method.
Teardown: Clean up resources allocated during setup or test execution.
The lifecycle is initiated and coordinated primarily through the `runtestprotocol` function:
def runtestprotocol(item: Item, log: bool = True, nextitem: Item | None = None) -> list[TestReport]:
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
return reports
The
call_and_reportfunction runs the corresponding phase hook (pytest_runtest_setup,pytest_runtest_call, orpytest_runtest_teardown) and generates aTestReport.If setup fails, the call phase is skipped, but teardown still executes to ensure proper cleanup.
The
nextitemargument helps optimize teardown by determining which resources need to be released based on upcoming tests.
SetupState: Managing Setup and Teardown Stack
The `SetupState` class maintains a stack representing active test nodes (e.g., session, modules, functions) to manage setup and teardown efficiently:
During setup, it pushes nodes (collectors and test items) onto the stack, running their setup code.
During teardown, it pops nodes off the stack, running their teardown finalizers.
It ensures that shared resources (like modules or sessions) are set up once and torn down only after all dependent tests complete.
This stack-based approach ensures that nested setups and teardowns occur correctly and that failures in setup propagate properly to dependent tests.
Outcome Handling and Reporting
Each phase’s result is wrapped in a
CallInfoobject capturing execution time, exceptions, and results.The
pytest_runtest_makereporthook converts these into richTestReportobjects for reporting.Special exceptions like
SkippedorExitcontrol flow or skip reporting as needed.Environment variable
PYTEST_CURRENT_TESTis updated dynamically to reflect the current test and phase, aiding tools and plugins that monitor test state.
Exception Interaction
When exceptions occur, pytest stores traceback information for postmortem debugging.
The system distinguishes between normal exceptions, skips, and control flow exceptions to decide when to enter interactive debugging or fail tests.
Relationship to Parent Topic and Other Subtopics
Test Lifecycle Management is a foundational part of the **Test Execution and Reporting** topic, implementing the detailed mechanics behind running each test item. While the parent topic covers the overall execution and reporting, this subtopic dives into:
The management of the test execution phases.
The stack-based setup and teardown coordination.
The lifecycle state transitions and error handling.
It complements **Terminal Reporting** and **JUnit XML Reporting** by producing the detailed reports those subtopics consume and display. It also interacts closely with **Fixture Management**, as fixture setup/teardown is integrated into the setup/teardown phases managed here.
Unlike the parent topic’s broad focus, this subtopic introduces the concept and implementation of the `SetupState` stack and the fine-grained lifecycle protocol, which are unique to managing test execution order and resource cleanup.
Diagram: Test Lifecycle Flowchart
flowchart TD
A[Start Test Run] --> B[Setup Phase]
B -->|Setup successful| C[Call Phase (Test Function Execution)]
B -->|Setup failed| D[Skip Call Phase]
C --> E[Teardown Phase]
D --> E
E --> F[Generate Test Reports]
F --> G[End Test Run]
style B fill:#a8d5e2,stroke:#333,stroke-width:1px
style C fill:#a8e2a8,stroke:#333,stroke-width:1px
style D fill:#f9d5a8,stroke:#333,stroke-width:1px
style E fill:#e2a8d5,stroke:#333,stroke-width:1px
style F fill:#d5e2a8,stroke:#333,stroke-width:1px
This flowchart illustrates the sequential phases of a test item’s lifecycle, emphasizing the conditional logic that skips the call phase if setup fails, and the mandatory teardown phase that always follows to ensure clean state.
This management of test lifecycle phases ensures pytest’s reliability and extensibility by clearly defining how tests are executed, how failures propagate, and how cleanup is guaranteed, forming the backbone for consistent test execution and reporting.