home / skills / pluginagentmarketplace / custom-plugin-python / pytest-testing

pytest-testing skill

/skills/pytest-testing

This skill helps you master pytest testing with fixtures, mocking, and CI/CD integration to improve code quality and test coverage.

npx playbooks add skill pluginagentmarketplace/custom-plugin-python --skill pytest-testing

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
8.6 KB
---
name: Pytest Testing
description: Master test-driven development with pytest, fixtures, mocking, and CI/CD integration
version: "2.1.0"
sasmp_version: "1.3.0"
bonded_agent: 04-testing-quality
bond_type: PRIMARY_BOND

# Skill Configuration
retry_strategy: exponential_backoff
observability:
  logging: true
  metrics: coverage_percent
---

# Pytest Testing

## Overview

Master software testing with pytest, Python's most popular testing framework. Learn test-driven development (TDD), write maintainable tests, and ensure code quality through comprehensive testing strategies.

## Learning Objectives

- Write unit, integration, and functional tests with pytest
- Use fixtures for test setup and teardown
- Mock external dependencies effectively
- Implement test-driven development (TDD)
- Measure and improve code coverage
- Integrate tests with CI/CD pipelines

## Core Topics

### 1. Pytest Basics
- Test discovery and naming conventions
- Assertions and comparison
- Test organization (files, classes, modules)
- Running tests (command-line options)
- Markers and test selection
- Parametrized tests

**Code Example:**
```python
# test_calculator.py
import pytest

def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# Basic test
def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

# Test exceptions
def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(10, 0)

# Parametrized test
@pytest.mark.parametrize("a,b,expected", [
    (10, 2, 5),
    (20, 4, 5),
    (100, 10, 10),
    (-10, 2, -5),
])
def test_divide(a, b, expected):
    assert divide(a, b) == expected

# Test with marker
@pytest.mark.slow
def test_complex_operation():
    # This test takes a long time
    result = sum(range(1000000))
    assert result == 499999500000
```

### 2. Fixtures & Test Setup
- Fixture scopes (function, class, module, session)
- Fixture dependencies
- Parametrized fixtures
- Built-in fixtures (tmpdir, capsys, monkeypatch)
- conftest.py for shared fixtures

**Code Example:**
```python
# conftest.py
import pytest
import tempfile
from pathlib import Path

@pytest.fixture
def sample_data():
    """Provide sample data for tests"""
    return {
        'users': [
            {'id': 1, 'name': 'Alice', 'email': '[email protected]'},
            {'id': 2, 'name': 'Bob', 'email': '[email protected]'},
        ]
    }

@pytest.fixture
def temp_file():
    """Create temporary file for testing"""
    with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
        f.write("Test data")
        temp_path = f.name
    yield temp_path
    # Cleanup
    Path(temp_path).unlink()

@pytest.fixture(scope='module')
def database_connection():
    """Module-scoped database connection"""
    db = DatabaseConnection('test.db')
    db.connect()
    yield db
    db.close()

# test_users.py
def test_user_count(sample_data):
    assert len(sample_data['users']) == 2

def test_user_names(sample_data):
    names = [user['name'] for user in sample_data['users']]
    assert 'Alice' in names
    assert 'Bob' in names

def test_file_operations(temp_file):
    content = Path(temp_file).read_text()
    assert content == "Test data"
```

### 3. Mocking & Test Doubles
- unittest.mock basics
- Mocking functions and methods
- Patching objects
- Mock assertions
- Side effects and return values
- Testing with external dependencies

**Code Example:**
```python
# api_client.py
import requests

class APIClient:
    def __init__(self, base_url):
        self.base_url = base_url

    def get_user(self, user_id):
        response = requests.get(f"{self.base_url}/users/{user_id}")
        response.raise_for_status()
        return response.json()

    def create_user(self, user_data):
        response = requests.post(f"{self.base_url}/users", json=user_data)
        response.raise_for_status()
        return response.json()

# test_api_client.py
from unittest.mock import Mock, patch
import pytest

@patch('api_client.requests.get')
def test_get_user(mock_get):
    # Setup mock
    mock_response = Mock()
    mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
    mock_response.raise_for_status.return_value = None
    mock_get.return_value = mock_response

    # Test
    client = APIClient('https://api.example.com')
    user = client.get_user(1)

    # Assertions
    assert user['name'] == 'Alice'
    mock_get.assert_called_once_with('https://api.example.com/users/1')

@patch('api_client.requests.post')
def test_create_user(mock_post):
    # Setup mock
    mock_response = Mock()
    mock_response.json.return_value = {'id': 3, 'name': 'Charlie'}
    mock_post.return_value = mock_response

    # Test
    client = APIClient('https://api.example.com')
    user_data = {'name': 'Charlie', 'email': '[email protected]'}
    result = client.create_user(user_data)

    # Assertions
    assert result['id'] == 3
    mock_post.assert_called_once_with(
        'https://api.example.com/users',
        json=user_data
    )
```

### 4. Coverage & CI/CD Integration
- Measuring code coverage with pytest-cov
- Coverage reports (terminal, HTML, XML)
- Setting coverage thresholds
- GitHub Actions integration
- GitLab CI integration
- Pre-commit hooks

**Code Example:**
```python
# pytest.ini
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
    --cov=myapp
    --cov-report=html
    --cov-report=term-missing
    --cov-fail-under=80
    -v

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov
      - name: Run tests
        run: |
          pytest --cov=myapp --cov-report=xml
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml

# Command line usage
# Run all tests
pytest

# Run with coverage
pytest --cov=myapp

# Generate HTML coverage report
pytest --cov=myapp --cov-report=html

# Run specific test file
pytest tests/test_api.py

# Run tests with marker
pytest -m slow

# Run tests with verbose output
pytest -v

# Stop on first failure
pytest -x
```

## Hands-On Practice

### Project 1: Calculator TDD
Build a calculator using test-driven development.

**Requirements:**
- Write tests BEFORE implementation
- Basic operations (add, subtract, multiply, divide)
- Error handling (division by zero)
- Scientific operations (power, sqrt, log)
- Test coverage > 90%

**Key Skills:** TDD workflow, parametrized tests, exception testing

### Project 2: API Testing Suite
Create comprehensive test suite for a REST API.

**Requirements:**
- Mock HTTP requests
- Test CRUD operations
- Error handling tests
- Authentication tests
- Integration tests
- CI/CD pipeline setup

**Key Skills:** Mocking, fixtures, integration testing

### Project 3: Database Testing
Test database operations with fixtures and transactions.

**Requirements:**
- Setup test database fixture
- Test CRUD operations
- Transaction rollback
- Data validation
- Performance tests
- Coverage report

**Key Skills:** Database fixtures, cleanup, performance testing

## Assessment Criteria

- [ ] Write clear, maintainable tests
- [ ] Use fixtures appropriately
- [ ] Mock external dependencies effectively
- [ ] Achieve >80% code coverage
- [ ] Follow TDD principles
- [ ] Integrate tests with CI/CD
- [ ] Write meaningful assertions

## Resources

### Official Documentation
- [Pytest Docs](https://docs.pytest.org/) - Official documentation
- [pytest-cov](https://pytest-cov.readthedocs.io/) - Coverage plugin
- [unittest.mock](https://docs.python.org/3/library/unittest.mock.html) - Mocking library

### Learning Platforms
- [Test-Driven Development with Python](https://www.obeythetestinggoat.com/) - TDD book
- [Python Testing with pytest](https://pragprog.com/titles/bopytest/) - Brian Okken's book
- [Real Python Testing](https://realpython.com/pytest-python-testing/) - Tutorials

### Tools
- [pytest-xdist](https://pytest-xdist.readthedocs.io/) - Parallel testing
- [pytest-mock](https://pytest-mock.readthedocs.io/) - Mocking helper
- [Hypothesis](https://hypothesis.readthedocs.io/) - Property-based testing
- [tox](https://tox.wiki/) - Testing automation

## Next Steps

After mastering pytest, explore:
- **Property-based testing** - Hypothesis library
- **Performance testing** - pytest-benchmark
- **Mutation testing** - mutmut
- **Load testing** - Locust, pytest-load

Overview

This skill teaches practical pytest testing for Python projects, focusing on TDD, fixtures, mocking, and CI/CD integration. It helps you write reliable unit, integration, and functional tests, measure coverage, and automate test runs in pipelines. You'll learn patterns that make tests maintainable and fast to run.

How this skill works

The skill walks through pytest fundamentals: discovery, assertions, markers, and parametrization, then covers fixtures for setup/teardown across scopes. It shows mocking with unittest.mock and patching external dependencies, plus coverage measurement with pytest-cov. Finally, it demonstrates CI integration (GitHub Actions/GitLab), pre-commit hooks, and real-world project exercises to apply TDD and test automation.

When to use it

  • When adopting test-driven development for new features
  • When adding reliable unit and integration tests to an existing codebase
  • When isolating external dependencies with mocks during testing
  • When enforcing coverage thresholds and reporting in CI/CD
  • When organizing shared test setup with fixtures and conftest.py

Best practices

  • Write tests before implementation to drive design and keep focus on requirements
  • Use fixtures for reusable setup and prefer narrow scopes unless costly resources demand wider scope
  • Mock network, file I/O, and external services to keep tests deterministic and fast
  • Parametrize tests to cover multiple input cases without duplication
  • Integrate pytest-cov in CI and set a realistic coverage threshold, not as the only quality gate

Example use cases

  • TDD-driven calculator: write failing tests, implement features, aim for >90% coverage
  • API test suite: mock HTTP calls, test CRUD flows, and add authentication scenarios
  • Database tests: use fixtures that create a test database and roll back transactions after each test
  • CI pipeline: run pytest with coverage in GitHub Actions, upload coverage XML to Codecov

FAQ

How do I choose fixture scope?

Prefer function scope for isolation; use module or session scope only for expensive resources like a real database connection and ensure proper cleanup.

When should I mock vs use integration tests?

Mock when you need fast, deterministic unit tests; add integration tests against real services sparingly to validate end-to-end behavior.

How to enforce coverage in CI without blocking development?

Set a baseline coverage threshold in CI and use incremental goals. Fail builds for significant regressions but avoid overly strict thresholds early on.