diff --git a/docs/bsn-agent-testaufgabe.md b/docs/bsn-agent-testaufgabe.md new file mode 100644 index 0000000..f71d6b0 --- /dev/null +++ b/docs/bsn-agent-testaufgabe.md @@ -0,0 +1,199 @@ +# BSN Coding Standards — Agenten-Testaufgabe + +> **Ziel:** Prüfen, ob ein lokaler Agent (z. B. Qwen 3.6) die BSN Coding Standards +> selbstständig einhält. Die Aufgabe ist bewusst klein — 15–20 Minuten für einen Agenten. + +--- + +## Aufgabe: Spiel-Bewertungs-Utility + +Schreibe ein Python-Modul `game_rating.py`, das Brettspiel-Bewertungen validiert +und einen Durchschnitt berechnet. Das Modul wird später in einer Flask-API verwendet. + +### Anforderungen + +1. **Funktion `validate_rating(wert: int) -> bool`:** + - Prüft, ob eine Bewertung zwischen 1 und 10 liegt + - Wirft `ValueError` bei ungültigen Werten + - **Deutscher Docstring (Google-Style) mit Args, Returns, Raises** + +2. **Funktion `berechne_durchschnitt(bewertungen: list[int]) -> float | None`:** + - Berechnet den Durchschnitt einer Bewertungsliste + - Gibt `None` zurück, wenn die Liste leer ist + - Jede Bewertung wird vorher mit `validate_rating()` geprüft + - **Vollständige Typ-Annotationen** (auch `-> None` wo nötig) + +3. **Konstante `MAX_BEWERTUNG`** = 10 (UPPER_SNAKE_CASE) + +4. **Tests in `test_game_rating.py`:** + - Test für gültige Bewertung + - Test für ungültige Bewertung (ValueError) + - Test für Durchschnitt mit mehreren Werten + - Test für leere Liste (None) + - Test für ungültigen Wert in der Liste (ValueError) + +### Was der Agent BEACHTEN muss (BSN Standards) + +| Regel | Woran erkennbar | +|---|---| +| Typ-Annotationen | Jede Funktion hat `->` mit korrektem Typ | +| `X \| None` | `-> float \| None`, NICHT `Optional[float]` | +| Deutsche Docstrings | Google-Style, auf Deutsch | +| `snake_case` | Funktionen und Variablen | +| `UPPER_SNAKE_CASE` | Konstante | +| Keine Secrets | Kein API-Key, kein Passwort im Code | +| Test-Namen | `test__` | + +### Erfolgskriterien (Automatisch prüfbar) + +```bash +# 1. Linting + Formatierung +ruff check game_rating.py test_game_rating.py --fix +ruff format game_rating.py test_game_rating.py + +# 2. Typ-Prüfung +mypy game_rating.py test_game_rating.py + +# 3. Tests +pytest test_game_rating.py -v + +# Alle drei müssen GRÜN sein. +``` + +### Abgabe + +Der Agent soll zwei Dateien erstellen: +- `game_rating.py` — die Implementierung +- `test_game_rating.py` — die Tests + +--- + +## Erwartete Lösung (Referenz — NICHT dem Agenten zeigen!) + +
+⚠️ Nur zum Nachschlagen — nicht vorab zeigen + +```python +# game_rating.py +"""Brettspiel-Bewertungs-Utility. + +Validiert Bewertungen und berechnet Durchschnittswerte. +""" + +MAX_BEWERTUNG = 10 + + +def validate_rating(wert: int) -> bool: + """Prüft, ob eine Bewertung im gültigen Bereich liegt. + + Args: + wert: Die zu prüfende Bewertung (1–10). + + Returns: + True, wenn die Bewertung gültig ist. + + Raises: + ValueError: Wenn die Bewertung außerhalb 1–10 liegt. + """ + if not isinstance(wert, int): + raise ValueError(f"Bewertung muss eine Ganzzahl sein, nicht {type(wert).__name__}") + if wert < 1 or wert > MAX_BEWERTUNG: + raise ValueError( + f"Bewertung {wert} ist ungültig. Erlaubt: 1–{MAX_BEWERTUNG}" + ) + return True + + +def berechne_durchschnitt(bewertungen: list[int]) -> float | None: + """Berechnet den Durchschnitt einer Bewertungsliste. + + Args: + bewertungen: Liste von Bewertungen (je 1–10). + + Returns: + Der Durchschnittswert oder None, wenn die Liste leer ist. + + Raises: + ValueError: Wenn eine Bewertung ungültig ist. + """ + if not bewertungen: + return None + + for wert in bewertungen: + validate_rating(wert) + + return sum(bewertungen) / len(bewertungen) +``` + +```python +# test_game_rating.py +"""Tests für das Spiel-Bewertungs-Utility.""" + +import pytest +from game_rating import MAX_BEWERTUNG, validate_rating, berechne_durchschnitt + + +class TestValidateRating: + """Tests für validate_rating().""" + + def test_valid_rating_returns_true(self) -> None: + """Gibt True für eine gültige Bewertung zurück.""" + assert validate_rating(5) is True + + def test_min_rating_returns_true(self) -> None: + """Gibt True für die minimale Bewertung (1) zurück.""" + assert validate_rating(1) is True + + def test_max_rating_returns_true(self) -> None: + """Gibt True für die maximale Bewertung (10) zurück.""" + assert validate_rating(MAX_BEWERTUNG) is True + + def test_zero_raises_valueerror(self) -> None: + """Wirft ValueError bei Bewertung 0.""" + with pytest.raises(ValueError, match="ungültig"): + validate_rating(0) + + def test_above_max_raises_valueerror(self) -> None: + """Wirft ValueError bei Bewertung > 10.""" + with pytest.raises(ValueError, match="ungültig"): + validate_rating(11) + + def test_negative_raises_valueerror(self) -> None: + """Wirft ValueError bei negativer Bewertung.""" + with pytest.raises(ValueError, match="ungültig"): + validate_rating(-1) + + def test_non_int_raises_valueerror(self) -> None: + """Wirft ValueError bei nicht-Integer-Wert.""" + with pytest.raises(ValueError, match="Ganzzahl"): + validate_rating(3.5) # type: ignore[arg-type] + + +class TestBerechneDurchschnitt: + """Tests für berechne_durchschnitt().""" + + def test_single_rating_returns_same(self) -> None: + """Gibt den Wert selbst bei einer einzelnen Bewertung zurück.""" + assert berechne_durchschnitt([7]) == 7.0 + + def test_multiple_ratings_returns_average(self) -> None: + """Berechnet den korrekten Durchschnitt mehrerer Bewertungen.""" + assert berechne_durchschnitt([6, 8, 10]) == 8.0 + + def test_empty_list_returns_none(self) -> None: + """Gibt None bei leerer Liste zurück.""" + assert berechne_durchschnitt([]) is None + + def test_invalid_rating_in_list_raises_valueerror(self) -> None: + """Wirft ValueError, wenn die Liste eine ungültige Bewertung enthält.""" + with pytest.raises(ValueError, match="ungültig"): + berechne_durchschnitt([5, 0, 7]) + + def test_float_result(self) -> None: + """Gibt Float zurück, auch wenn der Durchschnitt keine ganze Zahl ist.""" + result = berechne_durchschnitt([5, 8]) + assert result == 6.5 + assert isinstance(result, float) +``` + +