# -*- coding: utf-8 -*- """ Tests fuer den KeywordIntentMatcher. Testet Pattern-Parsing, Pattern-Matching, Fuzzy-Matching, Sentiment-Erkennung, Slot-Extraktion, Example-Matching und Confidence-Berechnung. """ import pytest from trixy_core.nlp.keyword_matcher import ( KeywordIntentMatcher, MatchResult, PatternToken, CompiledPattern, POLITE_WORDS, RUDE_WORDS, URGENT_WORDS, ) # ============================================================================ # Fixtures # ============================================================================ @pytest.fixture def matcher(): """Erstellt einen KeywordIntentMatcher mit Standard-Config.""" return KeywordIntentMatcher() @pytest.fixture def matcher_low_threshold(): """Matcher mit niedrigerer Schwelle fuer Grenzfall-Tests.""" return KeywordIntentMatcher({"confidence_threshold": 0.5, "fuzzy_threshold": 0.7}) @pytest.fixture def weather_intents(): """Wetter-Intent-Definitionen fuer Tests.""" return [ { "name": "current_weather", "patterns": [ "(wie ist|wie wird) [denn] [das] wetter [draussen|gerade|heute]", "wetter [in] {city}", "(wie warm|wie kalt) ist [es] [draussen|gerade]", "temperatur [draussen]", ], "examples": [ "Wie ist das Wetter?", "Wie ist das Wetter in Berlin?", "Wie warm ist es?", "Wetter in Hamburg", ], }, { "name": "weather_forecast", "patterns": [ "wie wird [das] wetter {date}", "vorhersage [fuer] {date}", "brauche ich {date} [einen] regenschirm", ], "examples": [ "Wie wird das Wetter morgen?", "Brauche ich morgen einen Regenschirm?", ], }, ] @pytest.fixture def system_intents(): """System-Intent-Definitionen fuer Tests.""" return [ { "name": "stop", "patterns": ["stopp", "halt", "alles stoppen", "sei still", "ruhe"], "examples": ["Stopp", "Halt", "Alles stoppen"], }, { "name": "pause", "patterns": ["pause", "pausieren"], "examples": ["Pause", "Pausieren"], }, { "name": "resume", "patterns": ["weiterspielen", "fortsetzen", "weiter [machen]", "play"], "examples": ["Weiterspielen", "Fortsetzen", "Play"], }, { "name": "volume_set", "patterns": [ "lautstaerke [auf] {level}", "(setze|stelle) [die] lautstaerke [auf] {level}", ], "examples": ["Lautstärke auf 50"], }, { "name": "help", "patterns": ["hilfe", "was kannst [du]", "befehle"], "examples": ["Hilfe", "Was kannst du?"], }, ] @pytest.fixture def timer_intents(): """Timer-Intent-Definitionen fuer Tests.""" return [ { "name": "set_timer", "patterns": [ "(setze|stelle|erstelle|starte) [einen|den] timer [auf|fuer] {duration}", "timer {duration}", "weck [mich] [in] {duration}", ], "examples": [ "Setze Timer auf 5 Minuten", "Timer 30 Sekunden", ], }, ] # ============================================================================ # Tokenisierung # ============================================================================ class TestTokenize: """Tests fuer die Tokenisierung.""" def test_einfacher_text(self, matcher): """Einfacher Text sollte in Kleinbuchstaben tokenisiert werden.""" tokens = matcher._tokenize("Wie ist das Wetter") assert tokens == ["wie", "ist", "das", "wetter"] def test_interpunktion_entfernt(self, matcher): """Interpunktion sollte entfernt werden.""" tokens = matcher._tokenize("Wie spät ist es?") assert "?" not in " ".join(tokens) assert tokens[0] == "wie" def test_umlaute_erhalten(self, matcher): """Umlaute sollten erhalten bleiben.""" tokens = matcher._tokenize("Wie spät ist es draußen?") assert "spät" in tokens or "spaet" in tokens or "spät" in tokens def test_leerer_text(self, matcher): """Leerer Text sollte leere Liste ergeben.""" tokens = matcher._tokenize("") assert tokens == [] def test_nur_leerzeichen(self, matcher): """Nur Leerzeichen sollte leere Liste ergeben.""" tokens = matcher._tokenize(" ") assert tokens == [] # ============================================================================ # Pattern-Parsing # ============================================================================ class TestParsePattern: """Tests fuer das Pattern-Parsing.""" def test_literal_keyword(self, matcher): """Einfaches Wort sollte als Literal geparst werden.""" tokens = matcher._parse_pattern("wetter") assert len(tokens) == 1 assert tokens[0].kind == "literal" assert tokens[0].values == ["wetter"] assert tokens[0].required is True def test_pflicht_alternativen(self, matcher): """(a|b) sollte als Pflicht-Alternativen geparst werden.""" tokens = matcher._parse_pattern("(wie ist|wie wird)") assert len(tokens) == 1 assert tokens[0].kind == "alternatives" assert "wie ist" in tokens[0].values assert "wie wird" in tokens[0].values assert tokens[0].required is True def test_optionales_wort(self, matcher): """[wort] sollte als optional geparst werden.""" tokens = matcher._parse_pattern("[denn]") assert len(tokens) == 1 assert tokens[0].kind == "optional" assert tokens[0].values == ["denn"] assert tokens[0].required is False def test_optionale_alternativen(self, matcher): """[a|b|c] sollte als optionale Alternativen geparst werden.""" tokens = matcher._parse_pattern("[draussen|gerade|heute]") assert len(tokens) == 1 assert tokens[0].kind == "optional" assert "draussen" in tokens[0].values assert "gerade" in tokens[0].values assert "heute" in tokens[0].values assert tokens[0].required is False def test_slot(self, matcher): """{{slot_name}} sollte als Slot geparst werden.""" tokens = matcher._parse_pattern("{city}") assert len(tokens) == 1 assert tokens[0].kind == "slot" assert tokens[0].slot_name == "city" assert tokens[0].entity_type == "" assert tokens[0].required is False def test_slot_mit_entity_typ(self, matcher): """{{name:typ}} sollte Slot mit Entity-Typ ergeben.""" tokens = matcher._parse_pattern("{date:datum}") assert len(tokens) == 1 assert tokens[0].kind == "slot" assert tokens[0].slot_name == "date" assert tokens[0].entity_type == "datum" def test_slot_mit_entity_typ_komplex(self, matcher): """Komplexes Pattern mit Entity-Typ-Slots.""" tokens = matcher._parse_pattern("wetter [am] {date:datum} [in] {city:stadt}") assert len(tokens) == 5 # {date:datum} assert tokens[2].kind == "slot" assert tokens[2].slot_name == "date" assert tokens[2].entity_type == "datum" # {city:stadt} assert tokens[4].kind == "slot" assert tokens[4].slot_name == "city" assert tokens[4].entity_type == "stadt" def test_inline_alternativen_mit_leer(self, matcher): """{a|b|} sollte als optional geparst werden (letztes leer).""" tokens = matcher._parse_pattern("{das|}") assert len(tokens) == 1 assert tokens[0].kind == "optional" assert "das" in tokens[0].values assert tokens[0].required is False def test_inline_alternativen_ohne_leer(self, matcher): """{a|b} sollte als Pflicht-Alternativen geparst werden.""" tokens = matcher._parse_pattern("{warm|kalt}") assert len(tokens) == 1 assert tokens[0].kind == "alternatives" assert "warm" in tokens[0].values assert "kalt" in tokens[0].values assert tokens[0].required is True def test_komplexes_pattern(self, matcher): """Komplexes Pattern mit mehreren Elementen.""" tokens = matcher._parse_pattern("(wie ist|wie wird) [denn] wetter [in] {city}") assert len(tokens) == 5 # (wie ist|wie wird) assert tokens[0].kind == "alternatives" assert tokens[0].required is True # [denn] assert tokens[1].kind == "optional" # wetter assert tokens[2].kind == "literal" assert tokens[2].required is True # [in] assert tokens[3].kind == "optional" # {city} assert tokens[4].kind == "slot" assert tokens[4].slot_name == "city" def test_leeres_pattern(self, matcher): """Leeres Pattern sollte leere Liste ergeben.""" tokens = matcher._parse_pattern("") assert tokens == [] # ============================================================================ # Pattern-Matching # ============================================================================ class TestPatternMatching: """Tests fuer das Pattern-Matching.""" def test_exakter_match(self, matcher, system_intents): """Exakter Befehl sollte matchen.""" matcher.compile_intents(system_intents) result = matcher.match("Stopp") assert result is not None assert result.intent == "stop" assert result.confidence >= 0.82 def test_einwort_befehle(self, matcher, system_intents): """Einwort-Befehle sollten korrekt matchen.""" matcher.compile_intents(system_intents) for text, expected_intent in [ ("Stopp", "stop"), ("Halt", "stop"), ("Pause", "pause"), ("Pausieren", "pause"), ("Hilfe", "help"), ("Play", "resume"), ]: result = matcher.match(text) assert result is not None, f"'{text}' sollte matchen" assert result.intent == expected_intent, ( f"'{text}' sollte '{expected_intent}' sein, war '{result.intent}'" ) def test_mehrwort_befehl(self, matcher, system_intents): """Mehrwort-Befehle sollten matchen.""" matcher.compile_intents(system_intents) result = matcher.match("Alles stoppen") assert result is not None assert result.intent == "stop" def test_optionale_woerter(self, matcher, system_intents): """Optionale Woerter sollten korrekt behandelt werden.""" matcher.compile_intents(system_intents) # "weiter [machen]" — ohne optionales Wort result = matcher.match("Weiter") assert result is not None assert result.intent == "resume" # "weiter [machen]" — mit optionalem Wort result = matcher.match("Weiter machen") assert result is not None assert result.intent == "resume" def test_pflicht_alternativen(self, matcher, weather_intents): """Pflicht-Alternativen sollten alle matchen.""" matcher.compile_intents(weather_intents) result1 = matcher.match("Wie ist das Wetter") assert result1 is not None assert result1.intent == "current_weather" result2 = matcher.match("Wie wird das Wetter") assert result2 is not None def test_slot_extraktion(self, matcher, weather_intents): """Slots sollten korrekt extrahiert werden.""" matcher.compile_intents(weather_intents) result = matcher.match("Wetter in Berlin") assert result is not None assert result.intent == "current_weather" assert "city" in result.slots assert result.slots["city"] == "berlin" def test_slot_mehrwort(self, matcher, weather_intents): """Slots sollten Mehrwort-Werte aufnehmen koennen.""" matcher.compile_intents(weather_intents) result = matcher.match("Wetter in New York") assert result is not None assert "city" in result.slots assert "new york" in result.slots["city"] def test_slot_am_ende(self, matcher, timer_intents): """Slot am Ende sollte restliche Tokens aufnehmen.""" matcher.compile_intents(timer_intents) result = matcher.match("Timer 5 Minuten") assert result is not None assert result.intent == "set_timer" assert "duration" in result.slots assert "5 minuten" in result.slots["duration"] def test_kein_match(self, matcher, system_intents): """Unbekannter Text sollte nicht matchen.""" matcher.compile_intents(system_intents) result = matcher.match("Wer war Beethoven?") assert result is None def test_kein_match_leerer_text(self, matcher, system_intents): """Leerer Text sollte nicht matchen.""" matcher.compile_intents(system_intents) assert matcher.match("") is None assert matcher.match(" ") is None assert matcher.match(None) is None # type: ignore def test_confidence_ueber_schwelle(self, matcher, system_intents): """Confidence sollte ueber der konfigurierten Schwelle liegen.""" matcher.compile_intents(system_intents) result = matcher.match("Stopp") assert result is not None assert result.confidence >= 0.82 def test_matched_by_pattern(self, matcher, system_intents): """matched_by sollte 'pattern' fuer Pattern-Matches sein.""" matcher.compile_intents(system_intents) result = matcher.match("Stopp") assert result is not None assert result.matched_by == "pattern" # ============================================================================ # Fuzzy-Matching # ============================================================================ class TestFuzzyMatching: """Tests fuer das Fuzzy-Matching (STT-Fehlertoleranz).""" def test_fuzzy_compare_exakt(self, matcher): """Exakte Matches sollten 1.0 ergeben.""" assert matcher._fuzzy_compare("wetter", "wetter") == 1.0 assert matcher._fuzzy_compare("stopp", "stopp") == 1.0 def test_fuzzy_compare_aehnlich(self, matcher): """Aehnliche Woerter sollten hohen Score ergeben.""" score = matcher._fuzzy_compare("weter", "wetter") assert score >= 0.8 def test_fuzzy_compare_verschieden(self, matcher): """Verschiedene Woerter sollten niedrigen Score ergeben.""" score = matcher._fuzzy_compare("hund", "katze") assert score < 0.5 def test_fuzzy_pattern_match(self, matcher, weather_intents): """Leichte STT-Fehler sollten toleriert werden.""" matcher.compile_intents(weather_intents) # "weter" statt "wetter" — ein Buchstabe fehlt result = matcher.match("weter in Berlin") assert result is not None assert result.intent == "current_weather" def test_fuzzy_example_match(self, matcher, system_intents): """Fuzzy sollte auch bei Example-Matching funktionieren.""" matcher.compile_intents(system_intents) # "Hilf" statt "Hilfe" — aehnlich genug result = matcher.match("Hilf") # Kann per Pattern oder Example matchen if result is not None: assert result.intent == "help" # ============================================================================ # Example-Matching # ============================================================================ class TestExampleMatching: """Tests fuer das Example-basierte Matching.""" def test_exakter_example_match(self, matcher): """Exakte Uebereinstimmung mit Example sollte hohen Score ergeben.""" score = matcher._score_example_match( ["wie", "ist", "das", "wetter"], ["wie", "ist", "das", "wetter"], ) assert score == 0.98 # Exakter Match def test_teilweise_uebereinstimmung(self, matcher): """Teilweise Uebereinstimmung sollte Score proportional ergeben.""" score = matcher._score_example_match( ["wie", "ist", "wetter"], ["wie", "ist", "das", "wetter"], ) assert score > 0.5 def test_leerer_input(self, matcher): """Leerer Input sollte 0.0 ergeben.""" assert matcher._score_example_match([], ["test"]) == 0.0 assert matcher._score_example_match(["test"], []) == 0.0 assert matcher._score_example_match([], []) == 0.0 def test_example_nur_intents(self, matcher): """Intents mit nur Examples (keine Patterns) sollten matchen.""" intents = [{ "name": "greet", "examples": ["Hallo", "Guten Tag", "Hi"], }] matcher.compile_intents(intents) result = matcher.match("Hallo") assert result is not None assert result.intent == "greet" assert result.matched_by == "example" def test_example_match_confidence(self, matcher): """Example-Matches sollten realistische Confidence haben.""" intents = [{ "name": "greet", "examples": ["Hallo wie geht es dir"], }] matcher.compile_intents(intents) result = matcher.match("Hallo wie geht es dir") assert result is not None assert result.confidence >= 0.82 # ============================================================================ # Sentiment-Erkennung # ============================================================================ class TestSentiment: """Tests fuer die Sentiment-Erkennung.""" def test_neutral(self, matcher): """Text ohne Floskel sollte neutral sein.""" sentiment = matcher._extract_sentiment(["wie", "ist", "das", "wetter"]) assert sentiment["tone"] == "neutral" assert sentiment["is_urgent"] is False assert sentiment["markers"] == [] def test_polite(self, matcher): """'bitte' sollte als hoeflich erkannt werden.""" sentiment = matcher._extract_sentiment(["bitte", "wie", "ist", "das", "wetter"]) assert sentiment["tone"] == "polite" assert "bitte" in sentiment["markers"] def test_rude(self, matcher): """Schimpfwoerter sollten als unhöflich erkannt werden.""" sentiment = matcher._extract_sentiment(["verdammt", "wie", "ist", "das", "wetter"]) assert sentiment["tone"] == "rude" assert "verdammt" in sentiment["markers"] def test_rude_ueberwiegt_polite(self, matcher): """Rude sollte polite ueberschreiben.""" sentiment = matcher._extract_sentiment(["bitte", "verdammt", "stopp"]) assert sentiment["tone"] == "rude" def test_urgent(self, matcher): """Dringlichkeitswoerter sollten erkannt werden.""" sentiment = matcher._extract_sentiment(["sofort", "stopp"]) assert sentiment["is_urgent"] is True assert "sofort" in sentiment["markers"] def test_polite_und_urgent(self, matcher): """Hoeflich und dringend gleichzeitig.""" sentiment = matcher._extract_sentiment(["bitte", "schnell", "stopp"]) assert sentiment["tone"] == "polite" assert sentiment["is_urgent"] is True def test_sentiment_in_match_result(self, matcher, system_intents): """Sentiment sollte im Match-Ergebnis enthalten sein.""" matcher.compile_intents(system_intents) result = matcher.match("bitte stopp") assert result is not None assert result.sentiment["tone"] == "polite" def test_sentiment_woerter_entfernt_beim_matching(self, matcher, weather_intents): """Sentiment-Woerter sollten das Matching nicht stoeren.""" matcher.compile_intents(weather_intents) # "bitte wie ist das wetter" — "bitte" soll entfernt werden result = matcher.match("bitte wie ist das Wetter") assert result is not None assert result.intent == "current_weather" assert result.sentiment["tone"] == "polite" # ============================================================================ # Compile-Intents # ============================================================================ class TestCompileIntents: """Tests fuer das Kompilieren von Intents.""" def test_compile_mit_patterns(self, matcher): """Intents mit Patterns sollten kompiliert werden.""" intents = [{ "name": "test_intent", "patterns": ["test (eins|zwei)"], "examples": ["Test eins"], }] matcher.compile_intents(intents) assert "test_intent" in matcher._compiled assert len(matcher._compiled["test_intent"]) == 1 def test_compile_nur_examples(self, matcher): """Intents mit nur Examples sollten in _examples landen.""" intents = [{ "name": "test_intent", "examples": ["Hallo", "Hi"], }] matcher.compile_intents(intents) assert "test_intent" not in matcher._compiled assert "test_intent" in matcher._examples assert len(matcher._examples["test_intent"]) == 2 def test_compile_ohne_name(self, matcher): """Intents ohne Name sollten uebersprungen werden.""" intents = [{"patterns": ["test"]}] matcher.compile_intents(intents) assert len(matcher._compiled) == 0 assert len(matcher._examples) == 0 def test_compile_leere_liste(self, matcher): """Leere Intent-Liste sollte keine Fehler verursachen.""" matcher.compile_intents([]) assert len(matcher._compiled) == 0 assert len(matcher._examples) == 0 def test_compile_loescht_vorherige(self, matcher): """Erneutes Kompilieren sollte vorherige Daten loeschen.""" matcher.compile_intents([{ "name": "old", "patterns": ["alt"], }]) assert "old" in matcher._compiled matcher.compile_intents([{ "name": "new", "patterns": ["neu"], }]) assert "old" not in matcher._compiled assert "new" in matcher._compiled def test_compile_mehrere_patterns_pro_intent(self, matcher): """Mehrere Patterns pro Intent sollten kompiliert werden.""" intents = [{ "name": "test", "patterns": ["muster eins", "(a|b) zwei", "drei [optional]"], }] matcher.compile_intents(intents) assert len(matcher._compiled["test"]) == 3 # ============================================================================ # Konfiguration # ============================================================================ class TestKonfiguration: """Tests fuer die Konfiguration des Matchers.""" def test_default_config(self): """Standard-Konfiguration sollte gesetzt sein.""" matcher = KeywordIntentMatcher() assert matcher._confidence_threshold == 0.82 assert matcher._fuzzy_threshold == 0.8 def test_custom_config(self): """Benutzerdefinierte Konfiguration sollte uebernommen werden.""" matcher = KeywordIntentMatcher({ "confidence_threshold": 0.9, "fuzzy_threshold": 0.85, }) assert matcher._confidence_threshold == 0.9 assert matcher._fuzzy_threshold == 0.85 def test_none_config(self): """None-Config sollte Standard-Werte verwenden.""" matcher = KeywordIntentMatcher(None) assert matcher._confidence_threshold == 0.82 def test_leere_config(self): """Leere Config sollte Standard-Werte verwenden.""" matcher = KeywordIntentMatcher({}) assert matcher._confidence_threshold == 0.82 # ============================================================================ # Datenklassen # ============================================================================ class TestDatenklassen: """Tests fuer die Datenklassen.""" def test_pattern_token_defaults(self): """PatternToken sollte korrekte Defaults haben.""" pt = PatternToken(kind="literal") assert pt.kind == "literal" assert pt.values == [] assert pt.slot_name == "" assert pt.required is True def test_compiled_pattern_defaults(self): """CompiledPattern sollte korrekte Defaults haben.""" cp = CompiledPattern() assert cp.tokens == [] assert cp.source == "" def test_match_result_defaults(self): """MatchResult sollte korrekte Defaults haben.""" mr = MatchResult() assert mr.intent == "" assert mr.confidence == 0.0 assert mr.slots == {} assert mr.sentiment == {} assert mr.matched_by == "" def test_match_result_mit_werten(self): """MatchResult sollte Werte korrekt speichern.""" mr = MatchResult( intent="test", confidence=0.95, slots={"city": "berlin"}, sentiment={"tone": "polite"}, matched_by="pattern", ) assert mr.intent == "test" assert mr.confidence == 0.95 assert mr.slots["city"] == "berlin" assert mr.sentiment["tone"] == "polite" # ============================================================================ # Sentiment-Wort-Entfernung # ============================================================================ class TestRemoveSentimentWords: """Tests fuer das Entfernen von Sentiment-Woertern.""" def test_entfernt_bitte(self, matcher): """'bitte' sollte entfernt werden.""" result = matcher._remove_sentiment_words(["bitte", "stopp"]) assert "bitte" not in result assert "stopp" in result def test_entfernt_fuellwoerter(self, matcher): """Fuellwoerter sollten entfernt werden.""" result = matcher._remove_sentiment_words(["kannst", "du", "mal", "stopp"]) assert "stopp" in result assert "kannst" not in result assert "du" not in result assert "mal" not in result def test_behaelt_original_bei_komplettem_entfernen(self, matcher): """Wenn alles entfernt wuerde, Original zurueckgeben.""" result = matcher._remove_sentiment_words(["bitte", "danke"]) # Sollte Original zurueckgeben, da sonst leere Liste assert len(result) > 0 # ============================================================================ # Integrationstests # ============================================================================ class TestIntegration: """Integrationstests mit realistischen Szenarien.""" @pytest.fixture def full_matcher(self, weather_intents, system_intents, timer_intents): """Matcher mit allen Intents geladen.""" m = KeywordIntentMatcher() all_intents = weather_intents + system_intents + timer_intents m.compile_intents(all_intents) return m def test_wetter_abfrage(self, full_matcher): """Wetter-Abfrage sollte korrekt matchen.""" result = full_matcher.match("Wie ist das Wetter?") assert result is not None assert result.intent == "current_weather" def test_wetter_mit_stadt(self, full_matcher): """Wetter-Abfrage mit Stadt sollte Slot extrahieren.""" result = full_matcher.match("Wetter in München") assert result is not None assert result.intent == "current_weather" assert "city" in result.slots def test_stopp_befehl(self, full_matcher): """Stopp-Befehl sollte matchen.""" result = full_matcher.match("Stopp") assert result is not None assert result.intent == "stop" def test_timer_setzen(self, full_matcher): """Timer setzen sollte matchen.""" result = full_matcher.match("Setze Timer auf 5 Minuten") assert result is not None assert result.intent == "set_timer" assert "duration" in result.slots def test_unbekannter_befehl(self, full_matcher): """Unbekannter Befehl sollte nicht matchen.""" result = full_matcher.match("Wer war Beethoven?") assert result is None def test_hoefliche_wetter_abfrage(self, full_matcher): """Hoefliche Anfrage sollte Sentiment erkennen.""" result = full_matcher.match("Bitte wie ist das Wetter?") assert result is not None assert result.intent == "current_weather" assert result.sentiment["tone"] == "polite" def test_dringende_stopp_anfrage(self, full_matcher): """Dringende Anfrage sollte Urgency erkennen.""" result = full_matcher.match("Sofort stopp") assert result is not None assert result.intent == "stop" assert result.sentiment["is_urgent"] is True def test_lautstaerke_mit_slot(self, full_matcher): """Lautstaerke setzen sollte Level-Slot extrahieren.""" result = full_matcher.match("Lautstärke auf 50") assert result is not None assert result.intent == "volume_set" assert "level" in result.slots def test_wie_warm_ist_es(self, full_matcher): """'Wie warm ist es' sollte als Wetter matchen.""" result = full_matcher.match("Wie warm ist es") assert result is not None assert result.intent == "current_weather" def test_pattern_match_vor_example(self, full_matcher): """Bei gleicher Confidence sollte der bessere Match gewinnen.""" result = full_matcher.match("Pause") assert result is not None assert result.intent == "pause" # Pattern-Match sollte bevorzugt werden (hoehere Confidence) # oder zumindest korrekt matchen def test_performance_einfach(self, full_matcher): """Einfacher Match sollte schnell sein (<10ms).""" import time start = time.perf_counter() for _ in range(100): full_matcher.match("Stopp") elapsed = (time.perf_counter() - start) / 100 * 1000 # ms # Sollte weit unter 10ms pro Match sein assert elapsed < 10, f"Match dauerte {elapsed:.2f}ms (sollte <10ms sein)" def test_performance_komplex(self, full_matcher): """Komplexer Match sollte schnell sein (<10ms).""" import time start = time.perf_counter() for _ in range(100): full_matcher.match("Bitte wie ist das Wetter in Berlin?") elapsed = (time.perf_counter() - start) / 100 * 1000 # ms assert elapsed < 10, f"Match dauerte {elapsed:.2f}ms (sollte <10ms sein)"