---
name: testing-python-pytest
description: >
  Generates professional pytest unit test suites for Python source files,
  ensuring >90% line and branch coverage per file. Analyzes modules with AST
  inspection to discover all classes, methods, and functions, then produces
  well-structured tests following the AAA pattern with fixtures, parametrize,
  mocking, and comprehensive edge cases. Use when asked to write unit tests,
  create a test suite, generate pytest tests, improve coverage, or test Python
  modules, classes, or functions — even if the user does not explicitly mention
  "pytest" or "coverage". Also use when CI pipelines report low test coverage.
compatibility: Requires Python >=3.8 and pip install pytest pytest-cov pytest-mock
license: MIT
metadata:
  author: skill-creator
  version: "1.1"
allowed-tools: Bash Read Write
---

# Python pytest Test Suite Generator

Generates comprehensive, production-quality pytest unit test suites that
achieve **>90% line and branch coverage per file**.

## Quick start

```bash
pip install pytest pytest-cov pytest-mock          # Einmalig installieren

python scripts/analyze_module.py src/my_module.py  # Modul analysieren
# → Schreibe tests/test_my_module.py  (siehe Workflow unten)
python scripts/run_coverage.py src/my_module.py tests/test_my_module.py
```

## Workflow

Kopiere diese Checkliste und hake ab:

```
Testing Progress:
- [ ] Step 1: Modul analysieren          → analyze_module.py
- [ ] Step 2: Testdatei schreiben        → AAA, Fixtures, Parametrize, Mocks
- [ ] Step 3: Coverage messen            → run_coverage.py
- [ ] Step 4: Lücken schließen           → Fehlende Zweige gezielt testen
- [ ] Step 5: ≥90 % bestätigt            → Fertig ✓
```

### Step 1 — Modul analysieren

```bash
python scripts/analyze_module.py <pfad/zum/modul.py>
```

Das Script gibt JSON aus mit: Klassen, Methoden, Funktionen, Typ-Hinweisen,
Exception-Pfaden, Async-Code und konkreten Testing-Hints.

### Step 2 — Testdatei schreiben

Dateiname: `tests/test_<modulname>.py`
Lese [references/PYTEST_PATTERNS.md](references/PYTEST_PATTERNS.md) für
Fixtures, Parametrize, Mocking und Async-Tests.

**Struktur jeder Testfunktion — AAA-Pflicht:**
```python
def test_<methode>_<szenario>(self, fixture):
    # Arrange  — Vorbedingungen und Eingaben
    input_val = ...
    # Act      — Aktion unter Test
    result = unit_under_test(input_val)
    # Assert   — Erwartetes Ergebnis prüfen
    assert result == expected
```

**Mindestabdeckung pro Einheit:**
| Szenario | Pflicht |
|---|---|
| Happy path (Normalfall) | ✓ |
| Grenzwerte (0, -1, leer, None) | ✓ |
| Exception-Pfade (`pytest.raises`) | ✓ je `raise` im Code |
| Alle Bedingungszweige (`if/else`) | ✓ |
| Externe Abhängigkeiten gemockt | ✓ |

### Step 3 & 4 — Coverage messen und Lücken schließen

```bash
python scripts/run_coverage.py <modul_pfad> <testdatei_pfad>
```

Das Script gibt aus: Gesamtabdeckung, nicht abgedeckte Zeilen, ob
der 90 %-Threshold erreicht wurde, und konkrete Empfehlungen.

**Feedback-Schleife:**
1. Uncovered lines lesen → betroffene Zeile im Quellcode finden
2. Welcher Branch/Condition ist nicht abgedeckt?
3. Gezielten Test hinzufügen → erneut messen
4. Wiederholen bis ≥90 %

### Step 5 — Abnahme

Coverage-Report zeigt ≥90 % für Zieldatei → Skill beendet.

## Test-Datei Musterstruktur

```python
"""Tests for <module_name>."""
import pytest
from unittest.mock import patch, MagicMock, call, ANY
from <package>.<module> import <ClassName>, <function_name>


# ── Fixtures ──────────────────────────────────────────────────────────────────

@pytest.fixture
def default_instance():
    """Standard-Instanz für Tests."""
    return <ClassName>(param="default")


@pytest.fixture
def mock_dependency(mocker):
    """Gemockte externe Abhängigkeit."""
    return mocker.patch("<package>.<module>.<ExternalClass>")


# ── <ClassName> ───────────────────────────────────────────────────────────────

class Test<ClassName>:
    """Tests für <ClassName>."""

    def test_init_sets_defaults(self):
        obj = <ClassName>()
        assert obj.attr == expected_default

    def test_<method>_returns_expected(self, default_instance):
        # Arrange
        ...
        # Act
        result = default_instance.<method>(...)
        # Assert
        assert result == expected

    @pytest.mark.parametrize("input,expected", [
        ("valid",   True),
        ("",        False),
        (None,      False),
    ])
    def test_<method>_parametrized(self, default_instance, input, expected):
        assert default_instance.<method>(input) == expected

    def test_<method>_raises_value_error_on_none(self, default_instance):
        with pytest.raises(ValueError, match="must not be None"):
            default_instance.<method>(None)

    def test_<method>_calls_dependency(self, default_instance, mock_dependency):
        default_instance.<method>("arg")
        mock_dependency.assert_called_once_with("arg")


# ── Modul-Funktionen ──────────────────────────────────────────────────────────

class Test<FunctionName>:
    def test_happy_path(self):
        assert <function_name>("valid") == expected

    def test_empty_input(self):
        assert <function_name>("") == []

    def test_raises_on_invalid_type(self):
        with pytest.raises(TypeError):
            <function_name>(42)
```

## Coverage-Anforderungen

| Metrik | Minimum |
|---|---|
| Zeilenabdeckung (je Datei) | ≥ 90 % |
| Branch-Abdeckung | ≥ 85 % |
| Öffentliche Methoden mit Test | 100 % |
| Exception-Pfade getestet | 100 % |

## Verfügbare Scripts

- **`scripts/analyze_module.py`** — AST-Analyse einer Python-Datei → JSON
- **`scripts/run_coverage.py`** — pytest + Coverage ausführen → JSON + Report

## Referenzen

- **[PYTEST_PATTERNS.md](references/PYTEST_PATTERNS.md)** — Fixtures, Parametrize, Mocking, Async, Patterns
- **[COVERAGE_CONFIG.md](references/COVERAGE_CONFIG.md)** — pytest.ini, .coveragerc, pyproject.toml
