Skip to content

Testing in Python with pytest (Version ≥ 8.2)

Overview

Testing in Python with pytest refers to using the pytest framework to design, execute, and manage automated tests with clear scope definition, coverage measurement, and CI/CD integration. Pytest is a mature, extensible testing framework that emphasizes simple syntax, powerful fixtures, and rich plugin support. It is widely adopted for unit testing, integration testing, and regression testing across Python packages and applications.

Testing Scope and Strategy

Test Scope Definition

Tests should be categorized by scope to ensure clarity, maintainability, and predictable execution.

Scope Type Purpose Typical Location
Unit Tests Validate isolated functions or methods tests/unit/
Integration Tests Validate interactions between components tests/integration/
End-to-End Tests Validate full application workflows tests/e2e/
Regression Tests Prevent reintroduction of known defects tests/regression/

Note

Scope discipline Clearly separating test scopes allows selective execution and faster feedback during development and CI runs.

Package-Oriented Test Layout

Pytest discovers tests based on file and function naming conventions.

Recommended structure:

(1) Test files prefixed with test_ or suffixed with _test.py

(2) Test functions prefixed with test_

(3) Tests colocated in a top-level tests/ directory

project/
├── src/
│   └── app/
├── tests/
│   ├── unit/
│   ├── integration/
│   └── e2e/
└── pytest.ini

Introduction to pytest

Core Features

Pytest provides the following core capabilities:

(1) Automatic test discovery

(2) Rich assertion introspection

(3) Fixture-based dependency injection

(4) Plugin ecosystem for coverage, mocking, and reporting

Minimal example:

def test_addition():
    assert 1 + 1 == 2

No explicit test runner or boilerplate is required.

Test Execution Flow

Pytest Runtime Lifecycle

When pytest executes tests, it follows a deterministic process.

(1) Load configuration from pytest.ini, pyproject.toml, or setup.cfg

(2) Discover test files and test functions

(3) Collect fixtures and resolve dependencies

(4) Execute tests in collection order

(5) Capture results, failures, and errors

(6) Generate reports and exit with a status code

Warning

Exit code dependency CI systems rely on pytest exit codes. Any test failure causes a non-zero exit code and should block deployments.

Test Coverage

Coverage Measurement

Coverage is commonly measured using the pytest-cov plugin, which integrates with coverage.py.

Typical coverage dimensions:

(1) Line coverage

(2) Branch coverage

(3) Module-level coverage

Example command:

pytest --cov=src --cov-report=term-missing

Tip

Coverage thresholds Define minimum coverage thresholds to prevent untested code from being merged.

Coverage Scope Control

Coverage should be scoped intentionally:

(1) Include only application packages

(2) Exclude test utilities and generated code

(3) Avoid inflating coverage with trivial paths

pytest.ini Configuration

Purpose of pytest.ini

The pytest.ini file centralizes pytest behavior and ensures consistent execution across environments.

Example pytest.ini (pytest ≥ 8.2)

[pytest]
minversion = 8.2
addopts = -ra --strict-markers
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
    unit: unit-level tests
    integration: integration-level tests
    e2e: end-to-end tests

Configuration responsibilities:

(1) Enforce minimum pytest version

(2) Define test discovery rules

(3) Register markers to avoid runtime warnings

(4) Standardize default CLI options

Note

Marker registration is mandatory Pytest ≥ 8.x enforces strict marker usage when --strict-markers is enabled.

Test Selection and Markers

Using Markers

Markers allow selective test execution by scope or purpose.

import pytest

@pytest.mark.unit
def test_parser():
    assert True

Selective execution:

pytest -m unit

Markers are essential for CI pipelines and large test suites.

CI/CD Integration

CI Test Pipeline Flow

In CI/CD systems, pytest is typically integrated as a mandatory quality gate.

(1) Install dependencies in an isolated environment

(2) Install test-only dependencies such as pytest and pytest-cov

(3) Execute pytest with coverage enabled

(4) Publish test and coverage reports

(5) Fail the pipeline on test or coverage violations

Example CI command:

pytest --cov=src --cov-report=xml

Warning

Pipeline stability requirement

Tests must be deterministic. Flaky tests undermine CI reliability and should be fixed or quarantined immediately.

Artifacts and Reporting

Common CI artifacts include:

(1) JUnit XML reports

(2) Coverage XML or HTML reports

(3) Logs from failed test cases

These artifacts support debugging and quality tracking.

Limitations and Constraints

Known Constraints

(1) Pytest does not enforce test isolation automatically

(2) Poor fixture design can lead to hidden coupling

(3) Excessive test runtime impacts CI feedback loops

Tip

Operational best practices

(1) Keep unit tests fast and isolated

(2) Push slow or external-dependency tests to integration scope

(3) Run the full test suite before release tagging

Reference