Files
planer2/backend/tests/test_struktur.py
2026-09-07 07:44:00 +02:00

326 lines
13 KiB
Python

import pruefung
import struktur
def _pruefe(bereich, feld, text):
return struktur.befunde(bereich, {feld: text})
def _probleme(befunde):
return [b["problem"] for b in befunde]
def test_satzzeichen_am_zeilenende():
b = _pruefe("funktion", "regeln", "- Passwort nie loggen.")[0]
assert b["problem"] == "Satzzeichen am Zeilenende"
assert b["vorschlag"] == "Passwort nie loggen"
def test_punkt_im_wort_ist_kein_satzende():
# Der Prüfer meldete hier „Punkt am Zeilenende" — die Zeile endet auf „v"
assert _pruefe("funktion", "regeln", "- API Zugänge stehen in .env") == []
assert _pruefe("funktion", "regeln", "- Nur kern und optional erlaubt") == []
def test_fuellwort_wird_entfernt():
b = _pruefe("funktion", "regeln", "- Auch Features entfernen")[0]
assert b["problem"] == "Füllwort „auch“"
assert b["vorschlag"] == "Features entfernen"
def test_liste_der_fuellwoerter_ist_abschliessend():
# Das Modell hielt „Im", „Nur" und „direkt" für Füllwörter — sind sie nicht
for zeile in ("- Im Modal Namen eingeben", "- Nur wenige Wörter",
"- Feature direkt in Vollmodus geöffnet"):
assert _pruefe("funktion", "regeln", zeile) == []
def test_wortgrenze_je_bereich_und_feld():
assert _probleme(_pruefe("feature", "titel", "Karten der Features prüfen")) \
== ["Mehr als 3 Wörter"]
assert _pruefe("funktion", "titel", "Karten der Features prüfen") == []
assert _probleme(_pruefe("funktion", "testen", "- Eins zwei drei vier fünf sechs sieben acht neun")) \
== ["Mehr als 8 Wörter"]
def test_ohne_vorschlag_wenn_der_code_nicht_kuerzen_kann():
b = _pruefe("funktion", "testen", "- Eins zwei drei vier fünf sechs sieben acht neun")[0]
assert b["vorschlag"] == "" # den Ersatz schreibt das Modell
def test_klammer_und_schraegstrich():
assert _probleme(_pruefe("funktion", "regeln", "- Rand blau (kern)")) \
== ["Klammer oder Schrägstrich"]
assert _probleme(_pruefe("funktion", "regeln", "- Kern/Optional getrennt")) \
== ["Klammer oder Schrägstrich"]
def test_ziel_saetze_und_laenge():
lang = "Dieses Ziel hat deutlich mehr als zwölf Wörter in einem einzigen langen Satz"
assert "Satz mit mehr als 12 Wörtern" in _probleme(_pruefe("feature", "ziel", lang))
assert _probleme(_pruefe("feature", "ziel", "Eins. Zwei. Drei")) == ["Mehr als 2 Sätze"]
def test_badge_nur_kern_oder_optional():
assert _pruefe("feature", "badge", "optional") == []
b = _pruefe("feature", "badge", "wichtig")[0]
assert b["vorschlag"] == "kern"
def test_jede_zeile_einzeln():
befunde = _pruefe("funktion", "regeln", "- Erste Regel.\n- Auch zweite Regel")
assert _probleme(befunde) == ["Satzzeichen am Zeilenende", "Füllwort „auch“"]
def test_ein_befund_je_zeile():
# Zwei Vorschläge für dieselbe Zeile wären nicht unabhängig anwendbar
befunde = _pruefe("funktion", "regeln", "- Auch Passwort prüfen.")
assert len(befunde) == 1
assert befunde[0]["problem"] == "Satzzeichen am Zeilenende, Füllwort „auch“"
assert befunde[0]["vorschlag"] == "Passwort prüfen"
def test_unreparierbares_zieht_den_ganzen_befund_zum_modell():
# Zu lang und Füllwort: der Code streicht nicht halb, das Modell schreibt neu
b = _pruefe("funktion", "testen", "- Auch eins zwei drei vier fünf sechs sieben acht")[0]
assert b["problem"] == "Füllwort „auch“, Mehr als 8 Wörter"
assert b["vorschlag"] == ""
def test_auftrag_nennt_feld_und_stelle():
b = _pruefe("feature", "titel", "Karten der Features prüfen")[0]
assert struktur.auftrag(b) == '- titel „Karten der Features prüfen“: Mehr als 3 Wörter'
async def test_messung_ist_auftrag_und_tuersteher(datenordner, monkeypatch):
"""Die Messung geht als Auftrag rein und prüft die Antwort wieder nach.
Ein Vorschlag, der weiter verstößt, wird zurückgewiesen — mit Begründung."""
import agents
import features
import projekte
projekte.anlegen("Test")
f = features.anlegen(
"Test",
"<!-- titel -->\nLogin\n<!-- funktion -->\nAnmelden\n"
"<!-- regeln -->\n- Auch Passwort prüfen.\n"
"<!-- testen -->\n- Anmelden\n- Angemeldet\n",
)
gesehen = []
# Jede Stimme liefert erst einen halbherzigen Vorschlag: Punkt weg, Füllwort bleibt
halb = '{"felder": {"regeln": {"neu": "- Auch Passwort prüfen", "grund": "Füllwort"}}}'
ganz = '{"felder": {"regeln": {"neu": "- Passwort prüfen", "grund": "Füllwort"}}}'
async def modell(system, inhalt, temperatur=0.0):
gesehen.append(inhalt)
return ganz if "Dein Vorschlag verstößt" in inhalt else halb
monkeypatch.setattr(agents, "frage", modell)
aufgabe = pruefung.aufgaben(f)[1]
probleme = await pruefung._pruefe({"funktion": "<KATALOG>"}, aufgabe)
# Eine Anfrage je Stimme, dazu je eine Nachbesserung
assert len(gesehen) == 2 * len(pruefung.TEMPERATUREN)
assert "Gemessen" in gesehen[0]
assert "Füllwort „auch“" in gesehen[0]
assert sum("Dein Vorschlag verstößt" in g for g in gesehen) == len(pruefung.TEMPERATUREN)
assert [p["original"] for p in probleme] == ["Auch Passwort prüfen."]
assert probleme[0]["vorschlag"] == "Passwort prüfen"
assert probleme[0]["feld"] == "regeln"
assert probleme[0]["funktion"] == "Anmelden"
async def test_gemessener_verstoss_wird_notfalls_selbst_behoben(datenordner, monkeypatch):
"""Ein gezählter Verstoß steht fest. Liefert keine Stimme einen brauchbaren
Ersatz, greift der Ersatz des Messers — sonst bliebe der Fehler stehen."""
import agents
import features
import projekte
projekte.anlegen("Test")
f = features.anlegen(
"Test",
"<!-- titel -->\nLogin\n<!-- funktion -->\nAnmelden\n"
"<!-- regeln -->\n- Auch Passwort prüfen.\n"
"<!-- testen -->\n- Anmelden\n- Angemeldet\n",
)
async def sturr(system, inhalt, temperatur=0.0):
return '{"felder": {"regeln": {"neu": "- Auch anders.", "grund": "x"}}}'
monkeypatch.setattr(agents, "frage", sturr)
probleme = await pruefung._pruefe({"funktion": "<KATALOG>"}, pruefung.aufgaben(f)[1])
assert [p["vorschlag"] for p in probleme] == ["Passwort prüfen"]
assert probleme[0]["regel"] == "Vom System gemessen"
def test_gemessene_befunde_haengen_am_ausschnitt(datenordner):
import features
import projekte
projekte.anlegen("Test")
f = features.anlegen("Test", "<!-- titel -->\nEin viel zu langer Kartentitel\n")
karte = pruefung.aufgaben(f)[0]
befunde = struktur.befunde("feature", karte["felder"])
assert "Gemessen" not in pruefung.inhalt(karte)
text = pruefung.inhalt(karte, befunde)
assert "Gemessen, bitte neu schreiben:" in text
assert "Mehr als 3 Wörter" in text
def test_funktionstitel_braucht_ein_verb():
"""Ohne Verb steht dort ein Bereich oder ein Zustand — nichts Testbares."""
for titel in ("Galerie im Modal", "Preis aktualisiert", "In den Warenkorb",
"Modal tastaturgesteuert"):
assert _probleme(_pruefe("funktion", "titel", titel)) \
== ["Kein Verb im Infinitiv am Ende"], titel
def test_gute_funktionstitel_bleiben_unberuehrt():
for titel in ("Feature anlegen", "Anzeigemodus umschalten", "Option wählen",
"Nächsten Rückschritt anzeigen", "Karten prüfen", "Stand umschalten",
"Projekt aufrufen", "Bildergalerie bedienen", "PDF hochladen"):
assert _pruefe("funktion", "titel", titel) == [], titel
def test_kartentitel_darf_ohne_verb_stehen():
"""Eine Karte ist ein Gegenstand, keine Handlung."""
assert _pruefe("feature", "titel", "Karten prüfen") == []
assert _pruefe("feature", "titel", "Darkmode") == []
ABLAUF = "- Vollmodus öffnen\n- LMB auf Regeln\n- Text ändern\n- Regeln angepasst"
def test_ein_ablauf_geht_durch():
assert _pruefe("funktion", "testen", ABLAUF) == []
def test_zweiter_ablauf_wird_gemeldet():
"""So entstand „Bildergalerie bedienen": blättern, Miniatur, Großansicht in einem."""
zwei = ("- Zurück-Button klicken\n- Vorheriges Bild erscheint\n"
"- Miniaturbild klicken\n- Zugehöriges Bild erscheint")
b = _pruefe("funktion", "testen", zwei)
assert _probleme(b) == ["Zweiter Ablauf in einer Funktion"]
assert b[0]["original"] == "Miniaturbild klicken"
def test_nominalstil_ist_eine_handlung():
"""„Bestätigen im Modal" hat das Verb vorn — sonst gälte es als Ergebnis."""
text = ("- RMB auf Featurecard\n- Klick auf \"Feature entfernen\"\n"
"- Bestätigen im Modal\n- Feature entfernt")
assert _pruefe("funktion", "testen", text) == []
def test_mehrzahl_auf_en_ist_kein_verb():
"""Deutsche Mehrzahl endet auch auf -en — nur Anfang und Ende zählen."""
text = "- LMB auf Icon\n- Fehlerhafte Karten rot markiert"
assert _pruefe("funktion", "testen", text) == []
def test_zeilenfehler_haben_vorrang():
"""Erst die Zeilen in Ordnung bringen; zwei Vorschläge je Feld wären nicht
unabhängig anwendbar."""
text = "- Auch klicken.\n- Ergebnis da\n- Weiter klicken"
assert _probleme(_pruefe("funktion", "testen", text)) \
== ["Satzzeichen am Zeilenende, Füllwort „auch“"]
def test_funktion_ohne_testschritte():
"""Ohne Ablauf ist der Titel eine Behauptung — der Prüfer stülpt ihn über alles."""
b = struktur.befunde("funktion", {"titel": "Modal steuern", "ziel": "",
"regeln": "", "testen": ""})
assert _probleme(b) == ["Funktion ohne Testschritte"]
def test_ziel_und_regeln_ersetzen_die_testschritte_nicht():
for feld in ("ziel", "regeln"):
felder = {"titel": "Modal steuern", "ziel": "", "regeln": "", "testen": ""}
felder[feld] = "Etwas tun" if feld == "ziel" else "- Etwas tun"
assert _probleme(struktur.befunde("funktion", felder)) \
== ["Funktion ohne Testschritte"], feld
def test_testschritte_genuegen():
assert struktur.befunde("funktion", {"titel": "Modal steuern", "ziel": "",
"regeln": "", "testen": "- Etwas tun"}) == []
def test_einzelnes_feld_loest_nichts_aus():
"""Beim Prüfen eines Feldes sagt das Fehlen der anderen nichts."""
assert struktur.befunde("funktion", {"titel": "Modal steuern"}) == []
def test_kartenebene_darf_leer_sein():
assert struktur.befunde("feature", {"titel": "Darkmode", "badge": "kern",
"ziel": "", "regeln": ""}) == []
def test_platzhalter_ist_kein_inhalt():
"""`<testschritt>` erfüllt jede Formregel und sagt trotzdem nichts."""
assert struktur._umschreiben("funktion", "testen", "<testschritt>")
assert struktur._umschreiben("funktion", "regeln", "Noch offen ...")
assert struktur._umschreiben("funktion", "regeln", "TODO klären")
def test_kleiner_als_ist_kein_platzhalter():
assert not struktur._umschreiben("funktion", "regeln", "Wert 3 < 5")
def test_kommando_als_testschritt():
"""Ein Setup wird über die Kommandozeile bedient — das muss zulässig sein."""
assert _pruefe("funktion", "testen", '- Befehl "make install" ausführen') == []
assert _pruefe("funktion", "titel", "Anwendung installieren") == []
def test_zitat_zaehlt_als_ein_wort():
"""Sonst sprengt jedes Kommando die Wortgrenze."""
lang = '- Befehl "uv run python -m tft.cli refresh" ausführen'
assert _pruefe("funktion", "testen", lang) == []
# Ohne Zitat greift die Grenze weiter
assert _probleme(_pruefe("funktion", "testen",
"- Ein sehr langer Schritt mit deutlich zu vielen Wörtern")) \
== ["Mehr als 8 Wörter"]
def test_schraegstrich_im_zitat_ist_erlaubt():
assert _pruefe("funktion", "testen", '- Befehl "npm run build/dev" ausführen') == []
assert _probleme(_pruefe("funktion", "regeln", "- Gilt für Bank und/oder Brett")) \
== ["Klammer oder Schrägstrich"]
def test_fuellwort_im_zitat_bleibt_stehen():
"""Der Menüeintrag heißt so — gestrichen zeigte der Schritt auf nichts."""
assert _pruefe("funktion", "testen", '- Klick auf "Auch Vorlagen zeigen"') == []
def test_kommandoschritt_ist_eine_handlung():
"""Nach dem Ergebnis darf keine Handlung mehr stehen — auch keine mit Kommando."""
zwei = ('- Befehl "make install" ausführen\n- Anwendung installiert\n'
'- Befehl "make test" ausführen')
assert _probleme(_pruefe("funktion", "testen", zwei)) == \
["Zweiter Ablauf in einer Funktion"]
def test_ausloeser_ist_kein_funktionstitel():
"""Der Titel nennt die Wirkung; der Griff steht in den Testschritten."""
for titel in ('Befehl "make install" ausführen', "LMB auf Speichern drücken",
"Klick auf Anlegen ausführen"):
assert "Auslöser statt Wirkung im Titel" in _probleme(
_pruefe("funktion", "titel", titel)), titel
assert _pruefe("funktion", "titel", "Anwendung installieren") == []
def test_vorbedingung_ist_kein_zweiter_ablauf():
"""Der Katalog erlaubt eine Vorbedingung zuerst — sie ist kein Endzustand."""
text = "- Badge ist sichtbar\n- RMB auf Badge\n- Text ändern\n- Neuer Text sichtbar"
assert _pruefe("funktion", "testen", text) == []
def test_zweiter_ablauf_faellt_weiter_auf():
text = "- Karte öffnen\n- Karte sichtbar\n- Karte schließen"
assert _probleme(_pruefe("funktion", "testen", text)) \
== ["Zweiter Ablauf in einer Funktion"]