266 lines
10 KiB
Python
266 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
🧪 Testsuite für Kapitel 14: Desktop-GUIs mit Tkinter & OOP
|
|
============================================================
|
|
Überprüft:
|
|
- ZaehlerLogik: Reines Datenmodell (Werte, Grenzen, Schrittweiten, Klicks)
|
|
- ZaehlerApp: Tkinter-Oberfläche, Widget-Erstellung, Event-Handling, MVC-Synchronisation
|
|
- Headless-Kompatibilität: Läuft ohne GUI-Blockade in jeder Testumgebung!
|
|
"""
|
|
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
# Pfad zum aktuellen Kapitel hinzufügen
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
try:
|
|
import aufgabe
|
|
except ImportError as e:
|
|
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
|
|
|
|
|
|
class TestKapitel14Logik(unittest.TestCase):
|
|
"""Prüft das reine Datenmodell ZaehlerLogik."""
|
|
|
|
def setUp(self):
|
|
ZaehlerLogik = getattr(aufgabe, "ZaehlerLogik", None)
|
|
self.assertIsNotNone(
|
|
ZaehlerLogik, "Klasse 'ZaehlerLogik' in aufgabe.py nicht gefunden!"
|
|
)
|
|
self.ZaehlerLogik = ZaehlerLogik
|
|
|
|
def test_01_logik_initialisierung(self):
|
|
"""Prüft Default-Werte und benutzerdefinierte Parameter."""
|
|
# Standard-Instanziierung
|
|
z1 = self.ZaehlerLogik()
|
|
self.assertEqual(z1.wert, 0, "Default-Startwert sollte 0 sein.")
|
|
self.assertEqual(z1.min_wert, -100, "Default min_wert sollte -100 sein.")
|
|
self.assertEqual(z1.max_wert, 100, "Default max_wert sollte 100 sein.")
|
|
self.assertEqual(z1.schrittweite, 1, "Default-Schrittweite sollte 1 sein.")
|
|
self.assertEqual(z1.klick_anzahl, 0, "Default-Klickanzahl sollte 0 sein.")
|
|
|
|
# Custom Instanziierung
|
|
z2 = self.ZaehlerLogik(startwert=10, min_wert=-20, max_wert=50)
|
|
self.assertEqual(z2.wert, 10)
|
|
self.assertEqual(z2.min_wert, -20)
|
|
self.assertEqual(z2.max_wert, 50)
|
|
|
|
def test_02_logik_erhoehen_und_verringern(self):
|
|
"""Prüft das Erhöhen und Verringern des Zählerstands und Zählen der Klicks."""
|
|
z = self.ZaehlerLogik(startwert=0)
|
|
|
|
# 1. Erhöhen
|
|
res = z.erhoehen()
|
|
self.assertEqual(res, 1, "erhoehen() sollte den neuen Wert (1) zurückgeben.")
|
|
self.assertEqual(z.wert, 1)
|
|
self.assertEqual(z.klick_anzahl, 1)
|
|
|
|
z.erhoehen()
|
|
self.assertEqual(z.wert, 2)
|
|
self.assertEqual(z.klick_anzahl, 2)
|
|
|
|
# 2. Verringern
|
|
res_v = z.verringern()
|
|
self.assertEqual(res_v, 1, "verringern() sollte den neuen Wert (1) zurückgeben.")
|
|
self.assertEqual(z.wert, 1)
|
|
self.assertEqual(z.klick_anzahl, 3)
|
|
|
|
z.verringern()
|
|
z.verringern()
|
|
self.assertEqual(z.wert, -1)
|
|
self.assertEqual(z.klick_anzahl, 5)
|
|
|
|
def test_03_logik_grenzwerte_clamping(self):
|
|
"""Prüft, dass der Wert min_wert und max_wert nicht überschreitet."""
|
|
z = self.ZaehlerLogik(startwert=9, min_wert=-5, max_wert=10)
|
|
|
|
# Erhöhen bis zum Limit
|
|
z.erhoehen() # wert = 10
|
|
self.assertEqual(z.wert, 10)
|
|
|
|
# Weiteres Erhöhen darf 10 nicht überschreiten
|
|
z.erhoehen()
|
|
self.assertEqual(z.wert, 10, "Zählerstand darf max_wert nicht überschreiten!")
|
|
|
|
# Verringern bis zum Minimal-Limit
|
|
z2 = self.ZaehlerLogik(startwert=-4, min_wert=-5, max_wert=10)
|
|
z2.verringern() # wert = -5
|
|
self.assertEqual(z2.wert, -5)
|
|
|
|
z2.verringern() # bleibt bei -5
|
|
self.assertEqual(z2.wert, -5, "Zählerstand darf min_wert nicht unterschreiten!")
|
|
|
|
def test_04_logik_schrittweite(self):
|
|
"""Prüft das Setzen und Anwenden der Schrittweite."""
|
|
z = self.ZaehlerLogik(startwert=0)
|
|
|
|
# Gültige Schrittweite
|
|
ok = z.setze_schrittweite(5)
|
|
self.assertTrue(ok, "setze_schrittweite(5) sollte True zurückgeben.")
|
|
self.assertEqual(z.schrittweite, 5)
|
|
|
|
z.erhoehen()
|
|
self.assertEqual(z.wert, 5, "erhoehen() sollte bei Schrittweite 5 um 5 steigen.")
|
|
|
|
z.verringern()
|
|
self.assertEqual(z.wert, 0)
|
|
|
|
# Ungültige Schrittweiten (0 oder negativ)
|
|
ok_null = z.setze_schrittweite(0)
|
|
self.assertFalse(ok_null, "setze_schrittweite(0) sollte False zurückgeben.")
|
|
self.assertEqual(z.schrittweite, 5, "Schrittweite darf sich bei 0 nicht ändern.")
|
|
|
|
ok_neg = z.setze_schrittweite(-3)
|
|
self.assertFalse(ok_neg, "setze_schrittweite(-3) sollte False zurückgeben.")
|
|
self.assertEqual(z.schrittweite, 5, "Schrittweite darf sich bei negativen Werten nicht ändern.")
|
|
|
|
def test_05_logik_zuruecksetzen(self):
|
|
"""Prüft das Zurücksetzen auf 0."""
|
|
z = self.ZaehlerLogik(startwert=42)
|
|
res = z.zuruecksetzen()
|
|
self.assertEqual(res, 0, "zuruecksetzen() sollte 0 zurückgeben.")
|
|
self.assertEqual(z.wert, 0)
|
|
self.assertEqual(z.klick_anzahl, 1)
|
|
|
|
def test_06_logik_ist_gerade_und_str(self):
|
|
"""Prüft die Geradheits-Prüfung und String-Repräsentation."""
|
|
z = self.ZaehlerLogik(startwert=0)
|
|
self.assertTrue(z.ist_gerade(), "0 ist gerade.")
|
|
|
|
z.erhoehen() # 1
|
|
self.assertFalse(z.ist_gerade(), "1 ist ungerade.")
|
|
|
|
z.erhoehen() # 2
|
|
self.assertTrue(z.ist_gerade(), "2 ist gerade.")
|
|
|
|
# __str__ Prüfung
|
|
text = str(z)
|
|
self.assertIn("2", text, "__str__ sollte den aktuellen Zählerstand enthalten.")
|
|
|
|
|
|
class TestKapitel14GUI(unittest.TestCase):
|
|
"""Prüft die Tkinter-Benutzeroberfläche ZaehlerApp im Headless-Modus."""
|
|
|
|
def setUp(self):
|
|
ZaehlerApp = getattr(aufgabe, "ZaehlerApp", None)
|
|
self.assertIsNotNone(
|
|
ZaehlerApp, "Klasse 'ZaehlerApp' in aufgabe.py nicht gefunden!"
|
|
)
|
|
self.ZaehlerApp = ZaehlerApp
|
|
|
|
# Hauptfenster (oder MockTk) erzeugen
|
|
tk_mod = getattr(aufgabe, "tk", None)
|
|
self.assertIsNotNone(tk_mod, "Tkinter-Modul 'tk' in aufgabe.py nicht gefunden!")
|
|
self.root = tk_mod.Tk()
|
|
self.app = self.ZaehlerApp(self.root)
|
|
|
|
def _get_widget_text(self, widget) -> str:
|
|
"""Hilfsfunktion zum Auslesen des Texts eines Widgets."""
|
|
if widget is None:
|
|
return ""
|
|
if hasattr(widget, "cget"):
|
|
try:
|
|
return str(widget.cget("text"))
|
|
except Exception:
|
|
pass
|
|
if hasattr(widget, "_text"):
|
|
return str(widget._text)
|
|
if isinstance(widget, dict):
|
|
return str(widget.get("text", ""))
|
|
return ""
|
|
|
|
def test_07_gui_initialisierung(self):
|
|
"""Prüft, ob alle Widgets angelegt wurden und die Startwerte stimmen."""
|
|
self.assertIsNotNone(getattr(self.app, "root", None), "app.root fehlt!")
|
|
self.assertIsNotNone(getattr(self.app, "logik", None), "app.logik fehlt!")
|
|
|
|
# Widgets überprüfen
|
|
self.assertTrue(
|
|
hasattr(self.app, "label_anzeige"), "app.label_anzeige fehlt!"
|
|
)
|
|
self.assertTrue(hasattr(self.app, "label_info"), "app.label_info fehlt!")
|
|
self.assertTrue(hasattr(self.app, "btn_plus"), "app.btn_plus fehlt!")
|
|
self.assertTrue(hasattr(self.app, "btn_minus"), "app.btn_minus fehlt!")
|
|
self.assertTrue(hasattr(self.app, "btn_reset"), "app.btn_reset fehlt!")
|
|
self.assertTrue(hasattr(self.app, "entry_schritt"), "app.entry_schritt fehlt!")
|
|
|
|
# Initialer Text im Anzeige-Label sollte "0" sein
|
|
anzeige_text = self._get_widget_text(self.app.label_anzeige)
|
|
self.assertEqual(
|
|
anzeige_text, "0", f"Startwert im Anzeige-Label sollte '0' sein, war aber '{anzeige_text}'"
|
|
)
|
|
|
|
def test_08_gui_klick_plus_und_minus(self):
|
|
"""Prüft, ob klick_plus und klick_minus die Logik und das Label aktualisieren."""
|
|
# 1. Plus-Klick auslösen
|
|
self.app.klick_plus()
|
|
self.assertEqual(self.app.logik.wert, 1, "Logik-Wert sollte 1 sein.")
|
|
self.assertEqual(
|
|
self._get_widget_text(self.app.label_anzeige),
|
|
"1",
|
|
"Anzeige-Label sollte nach Plus-Klick '1' zeigen.",
|
|
)
|
|
|
|
# 2. Weiterer Plus-Klick
|
|
self.app.klick_plus()
|
|
self.assertEqual(self.app.logik.wert, 2)
|
|
self.assertEqual(self._get_widget_text(self.app.label_anzeige), "2")
|
|
|
|
# 3. Minus-Klick
|
|
self.app.klick_minus()
|
|
self.assertEqual(self.app.logik.wert, 1)
|
|
self.assertEqual(self._get_widget_text(self.app.label_anzeige), "1")
|
|
|
|
def test_09_gui_klick_reset(self):
|
|
"""Prüft, ob klick_reset den Zähler und das Label zurücksetzt."""
|
|
self.app.klick_plus()
|
|
self.app.klick_plus()
|
|
self.assertEqual(self.app.logik.wert, 2)
|
|
|
|
self.app.klick_reset()
|
|
self.assertEqual(self.app.logik.wert, 0, "Logik-Wert sollte 0 nach Reset sein.")
|
|
self.assertEqual(
|
|
self._get_widget_text(self.app.label_anzeige),
|
|
"0",
|
|
"Anzeige-Label sollte nach Reset '0' zeigen.",
|
|
)
|
|
|
|
def test_10_gui_schrittweite_setzen(self):
|
|
"""Prüft, ob klick_schritt_setzen den Wert aus entry_schritt übernimmt."""
|
|
# Wert im Entry-Feld anpassen
|
|
self.app.entry_schritt.delete(0, "end")
|
|
self.app.entry_schritt.insert(0, "10")
|
|
|
|
# Setzen auslösen
|
|
self.app.klick_schritt_setzen()
|
|
self.assertEqual(
|
|
self.app.logik.schrittweite,
|
|
10,
|
|
"Schrittweite in Logik sollte auf 10 gesetzt werden.",
|
|
)
|
|
|
|
# Plus klicken -> Sprung um 10
|
|
self.app.klick_plus()
|
|
self.assertEqual(self.app.logik.wert, 10)
|
|
self.assertEqual(self._get_widget_text(self.app.label_anzeige), "10")
|
|
|
|
def test_11_gui_ungueltige_eingabe_abfangen(self):
|
|
"""Prüft robuste Fehlerbehandlung bei ungültigen Entry-Eingaben."""
|
|
# Text statt Zahl eingeben
|
|
self.app.entry_schritt.delete(0, "end")
|
|
self.app.entry_schritt.insert(0, "keine_zahl")
|
|
|
|
# Darf keinen Absturz/Exception erzeugen
|
|
try:
|
|
self.app.klick_schritt_setzen()
|
|
except Exception as e:
|
|
self.fail(f"klick_schritt_setzen() ist bei ungültiger Eingabe abgestürzt: {e}")
|
|
|
|
# Schrittweite sollte unverändert geblieben sein
|
|
self.assertEqual(self.app.logik.schrittweite, 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|