#!/usr/bin/env python3 """ 🧪 Umfassende Unittest-Suite für Kapitel 16 (Master-Abschlussprojekt) ===================================================================== Prüft: - Teil 1: Exception-Hierarchie & Vererbung - Teil 2: Datenmodelle (Basisklasse Tier & Kindklassen Hund, Katze, Vogel) - Teil 3: Polymorphie & Factory-Muster (Tier.from_dict) - Teil 4: Geschäftslogik (Tierheim, Kapazität, Filter, Massenoperationen, Statistik) - Teil 5: Persistenz (JSON Savegame-Roundtrip & CSV-Export) - Teil 6: GUI-Controller (TierheimApp im Headless-Modus) """ import csv import json import os import sys import tempfile import unittest from pathlib import Path # Aktuellen Ordner in sys.path einfü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 Test01Exceptions(unittest.TestCase): """Prüft die Exception-Klassen und deren Vererbungshierarchie.""" def test_01_exceptions_existieren_und_erben(self): self.assertTrue(hasattr(aufgabe, "TierheimFehler"), "TierheimFehler fehlt!") self.assertTrue(hasattr(aufgabe, "ValidierungsFehler"), "ValidierungsFehler fehlt!") self.assertTrue(hasattr(aufgabe, "TierNichtGefundenFehler"), "TierNichtGefundenFehler fehlt!") self.assertTrue(hasattr(aufgabe, "KapazitaetUeberschrittenFehler"), "KapazitaetUeberschrittenFehler fehlt!") self.assertTrue(issubclass(aufgabe.TierheimFehler, Exception)) self.assertTrue(issubclass(aufgabe.ValidierungsFehler, aufgabe.TierheimFehler)) self.assertTrue(issubclass(aufgabe.TierNichtGefundenFehler, aufgabe.TierheimFehler)) self.assertTrue(issubclass(aufgabe.KapazitaetUeberschrittenFehler, aufgabe.TierheimFehler)) class Test02TierBasisklasse(unittest.TestCase): """Prüft die Basisklasse Tier inkl. Validierung und Basis-Methoden.""" def test_02_tier_initialisierung_und_attribute(self): t = aufgabe.Tier("Sammy", alter=4, gewicht=12.5, geimpft=False, hunger=40) self.assertEqual(t.name, "Sammy") self.assertEqual(t.alter, 4) self.assertAlmostEqual(t.gewicht, 12.5) self.assertFalse(t.geimpft) self.assertEqual(t.hunger, 40) def test_03_tier_validierung_leerer_name(self): with self.assertRaises(aufgabe.ValidierungsFehler): aufgabe.Tier("", alter=3, gewicht=5.0) with self.assertRaises(aufgabe.ValidierungsFehler): aufgabe.Tier(" ", alter=3, gewicht=5.0) def test_04_tier_validierung_negatives_alter(self): with self.assertRaises(aufgabe.ValidierungsFehler): aufgabe.Tier("Sammy", alter=-1, gewicht=5.0) def test_05_tier_validierung_ungueltiges_gewicht(self): with self.assertRaises(aufgabe.ValidierungsFehler): aufgabe.Tier("Sammy", alter=2, gewicht=0.0) with self.assertRaises(aufgabe.ValidierungsFehler): aufgabe.Tier("Sammy", alter=2, gewicht=-4.5) def test_06_tier_fuettern_und_clamping(self): t = aufgabe.Tier("Sammy", alter=2, gewicht=5.0, hunger=60) msg = t.fuettern(100) # -20 Hunger self.assertEqual(t.hunger, 40) self.assertIn("Sammy", msg) # Überfüttern darf nicht unter 0 fallen t.fuettern(500) self.assertEqual(t.hunger, 0) def test_07_tier_impfen(self): t = aufgabe.Tier("Sammy", alter=2, gewicht=5.0, geimpft=False) erstes_mal = t.impfen() self.assertTrue(erstes_mal, "Erstmaliges Impfen sollte True zurückgeben.") self.assertTrue(t.geimpft) zweites_mal = t.impfen() self.assertFalse(zweites_mal, "Erneutes Impfen sollte False zurückgeben.") def test_08_tier_to_dict_und_str(self): t = aufgabe.Tier("Sammy", alter=2, gewicht=5.0, geimpft=True, hunger=30) d = t.to_dict() self.assertEqual(d.get("name"), "Sammy") self.assertEqual(d.get("alter"), 2) self.assertEqual(d.get("gewicht"), 5.0) self.assertEqual(d.get("geimpft"), True) self.assertEqual(d.get("hunger"), 30) self.assertIn("Sammy", str(t)) class Test03Kindklassen(unittest.TestCase): """Prüft Vererbung und Spezialisierungen für Hund, Katze und Vogel.""" def test_09_hund_spezifisch(self): h = aufgabe.Hund("Bello", alter=3, gewicht=15.0, rasse="Labrador", geimpft=False, hunger=40) self.assertTrue(isinstance(h, aufgabe.Tier), "Hund muss von Tier erben!") self.assertEqual(h.rasse, "Labrador") self.assertFalse(h.gassigegangen) # Gassigehen msg = h.gassi_gehen(20) # +4 Hunger self.assertTrue(h.gassigegangen) self.assertEqual(h.hunger, 44) self.assertIn("Bello", msg) # Laut laut = h.mache_laut() self.assertIn("Wuff", laut) # to_dict d = h.to_dict() self.assertEqual(d.get("art"), "Hund") self.assertEqual(d.get("rasse"), "Labrador") self.assertTrue(d.get("gassigegangen")) def test_10_katze_spezifisch(self): k = aufgabe.Katze("Luna", alter=2, gewicht=4.0, stubenrein=True, hunger=30) self.assertTrue(isinstance(k, aufgabe.Tier), "Katze muss von Tier erben!") self.assertTrue(k.stubenrein) self.assertFalse(k.kratzbaum_benutzt) # Kratzen k.kratzen() self.assertTrue(k.kratzbaum_benutzt) # Laut laut = k.mache_laut() self.assertIn("Miau", laut) # to_dict d = k.to_dict() self.assertEqual(d.get("art"), "Katze") self.assertTrue(d.get("stubenrein")) self.assertTrue(d.get("kratzbaum_benutzt")) def test_11_vogel_spezifisch(self): v = aufgabe.Vogel("Tweety", alter=1, gewicht=0.3, spannweite_cm=18.0, kann_sprechen=True, hunger=20) self.assertTrue(isinstance(v, aufgabe.Tier), "Vogel muss von Tier erben!") self.assertEqual(v.spannweite_cm, 18.0) self.assertTrue(v.kann_sprechen) # Fliegen v.fliegen(5) # +10 Hunger self.assertEqual(v.hunger, 30) # Laut mit Sprechen laut = v.mache_laut() self.assertIn("Hallo Mensch", laut) # Vogel ohne Sprechen v2 = aufgabe.Vogel("Charly", alter=2, gewicht=0.4, kann_sprechen=False) laut2 = v2.mache_laut() self.assertIn("Tschilp", laut2) class Test04PolymorphieUndFactory(unittest.TestCase): """Prüft das Factory-Pattern Tier.from_dict für alle Subklassen.""" def test_12_factory_hund_wiederherstellen(self): data = { "art": "Hund", "name": "Rex", "alter": 5, "gewicht": 20.0, "rasse": "Schäferhund", "geimpft": True, "hunger": 35, "gassigegangen": True, } obj = aufgabe.Tier.from_dict(data) self.assertIsInstance(obj, aufgabe.Hund) self.assertEqual(obj.name, "Rex") self.assertEqual(obj.rasse, "Schäferhund") self.assertTrue(obj.geimpft) self.assertTrue(obj.gassigegangen) def test_13_factory_katze_wiederherstellen(self): data = { "art": "Katze", "name": "Minka", "alter": 3, "gewicht": 3.8, "stubenrein": True, "geimpft": False, "hunger": 50, "kratzbaum_benutzt": False, } obj = aufgabe.Tier.from_dict(data) self.assertIsInstance(obj, aufgabe.Katze) self.assertEqual(obj.name, "Minka") self.assertTrue(obj.stubenrein) def test_14_factory_vogel_wiederherstellen(self): data = { "art": "Vogel", "name": "Kiki", "alter": 2, "gewicht": 0.2, "spannweite_cm": 15.5, "kann_sprechen": True, "geimpft": True, "hunger": 10, } obj = aufgabe.Tier.from_dict(data) self.assertIsInstance(obj, aufgabe.Vogel) self.assertEqual(obj.name, "Kiki") self.assertEqual(obj.spannweite_cm, 15.5) self.assertTrue(obj.kann_sprechen) class Test05TierheimGeschaeftslogik(unittest.TestCase): """Prüft die Model-Klasse Tierheim (Aufnahme, Entlassen, Filter, Statistik).""" def setUp(self): self.heim = aufgabe.Tierheim("Tierheim Test", max_kapazitaet=4) self.bello = aufgabe.Hund("Bello", alter=4, gewicht=15.0, geimpft=True, hunger=20) self.luna = aufgabe.Katze("Luna", alter=2, gewicht=4.0, geimpft=False, hunger=60) self.tweety = aufgabe.Vogel("Tweety", alter=1, gewicht=0.3, geimpft=False, hunger=70) def test_15_aufnahme_und_kapazitaet(self): self.heim.tier_aufnehmen(self.bello) self.heim.tier_aufnehmen(self.luna) self.heim.tier_aufnehmen(self.tweety) self.assertEqual(len(self.heim), 3) # 4. Tier aufnehmen (Max erreicht) h2 = aufgabe.Hund("Rocky", alter=1, gewicht=10.0) self.heim.tier_aufnehmen(h2) self.assertEqual(len(self.heim), 4) # 5. Tier aufnehmen -> KapazitaetUeberschrittenFehler h3 = aufgabe.Hund("Pluto", alter=2, gewicht=8.0) with self.assertRaises(aufgabe.KapazitaetUeberschrittenFehler): self.heim.tier_aufnehmen(h3) def test_16_tier_entlassen_und_suche(self): self.heim.tier_aufnehmen(self.bello) self.heim.tier_aufnehmen(self.luna) # Suchen gefunden = self.heim.finde_tier("bello") self.assertIsNotNone(gefunden) self.assertEqual(gefunden.name, "Bello") # Entlassen entlassen = self.heim.tier_entlassen("Bello") self.assertEqual(entlassen.name, "Bello") self.assertEqual(len(self.heim), 1) # Erneutes Entlassen -> TierNichtGefundenFehler with self.assertRaises(aufgabe.TierNichtGefundenFehler): self.heim.tier_entlassen("Bello") def test_17_filter_nach_art(self): self.heim.tier_aufnehmen(self.bello) self.heim.tier_aufnehmen(self.luna) self.heim.tier_aufnehmen(self.tweety) hunde = self.heim.filtriere_nach_art("Hund") self.assertEqual(len(hunde), 1) self.assertEqual(hunde[0].name, "Bello") katzen = self.heim.filtriere_nach_art("Katze") self.assertEqual(len(katzen), 1) self.assertEqual(katzen[0].name, "Luna") alle = self.heim.filtriere_nach_art("Alle") self.assertEqual(len(alle), 3) def test_18_filter_ungeimpft_und_hungrig(self): self.heim.tier_aufnehmen(self.bello) # Geimpft, Hunger 20 self.heim.tier_aufnehmen(self.luna) # Ungeimpft, Hunger 60 self.heim.tier_aufnehmen(self.tweety) # Ungeimpft, Hunger 70 ungeimpft = self.heim.ungeimpfte_tiere() self.assertEqual(len(ungeimpft), 2) hungrig = self.heim.hungrige_tiere(schwellenwert=50) self.assertEqual(len(hungrig), 2) def test_19_statistik_berechnungen(self): self.heim.tier_aufnehmen(self.bello) # 4 J., 15.0 kg self.heim.tier_aufnehmen(self.luna) # 2 J., 4.0 kg self.heim.tier_aufnehmen(self.tweety) # 1 J., 0.3 kg # Alter: (4 + 2 + 1) / 3 = 2.333 self.assertAlmostEqual(self.heim.durchschnittsalter(), 7.0 / 3.0, places=2) # Gewicht: 15.0 + 4.0 + 0.3 = 19.3 self.assertAlmostEqual(self.heim.gesamtgewicht(), 19.3, places=2) def test_20_massenaktionen_fuettern_und_impfen(self): self.heim.tier_aufnehmen(self.bello) # Geimpft, Hunger 20 self.heim.tier_aufnehmen(self.luna) # Ungeimpft, Hunger 60 self.heim.tier_aufnehmen(self.tweety) # Ungeimpft, Hunger 70 # Alle impfen -> 2 neu geimpft frisch = self.heim.alle_impfen() self.assertEqual(frisch, 2) self.assertEqual(len(self.heim.ungeimpfte_tiere()), 0) # Alle füttern (100g -> -20 Hunger) ergebnisse = self.heim.alle_fuettern(100) self.assertEqual(len(ergebnisse), 3) self.assertEqual(self.bello.hunger, 0) self.assertEqual(self.luna.hunger, 40) self.assertEqual(self.tweety.hunger, 50) class Test06Persistenz(unittest.TestCase): """Prüft JSON Savegame und CSV Export.""" def test_21_json_speichern_und_laden(self): heim = aufgabe.Tierheim("Savegame Heim", max_kapazitaet=10) heim.tier_aufnehmen(aufgabe.Hund("Bello", 3, 14.5, rasse="Husky", geimpft=True)) heim.tier_aufnehmen(aufgabe.Katze("Luna", 2, 4.2, stubenrein=True, geimpft=False)) heim.tier_aufnehmen(aufgabe.Vogel("Tweety", 1, 0.3, spannweite_cm=19.0, kann_sprechen=True)) with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tf: pfad = tf.name try: heim.speichern_json(pfad) self.assertTrue(os.path.exists(pfad)) # Neues leeres Heim lädt den Stand neues_heim = aufgabe.Tierheim("Leer", max_kapazitaet=5) neues_heim.laden_json(pfad) self.assertEqual(neues_heim.name, "Savegame Heim") self.assertEqual(len(neues_heim), 3) # Prüfen ob Typen erhalten blieben (Polymorphie) self.assertIsInstance(neues_heim.tiere[0], aufgabe.Hund) self.assertEqual(neues_heim.tiere[0].rasse, "Husky") self.assertIsInstance(neues_heim.tiere[1], aufgabe.Katze) self.assertIsInstance(neues_heim.tiere[2], aufgabe.Vogel) self.assertTrue(neues_heim.tiere[2].kann_sprechen) finally: if os.path.exists(pfad): os.remove(pfad) def test_22_csv_export(self): heim = aufgabe.Tierheim("CSV Heim", max_kapazitaet=10) heim.tier_aufnehmen(aufgabe.Hund("Bello", 3, 14.5, rasse="Husky", geimpft=True)) heim.tier_aufnehmen(aufgabe.Katze("Luna", 2, 4.2, stubenrein=True, geimpft=False)) with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as tf: pfad = tf.name try: heim.exportiere_csv(pfad) self.assertTrue(os.path.exists(pfad)) with open(pfad, "r", encoding="utf-8") as f: reader = list(csv.reader(f)) # Header prüfen self.assertGreaterEqual(len(reader), 3) self.assertEqual(reader[0], ["Art", "Name", "Alter", "Gewicht", "Geimpft", "Hunger", "Details"]) # Erste Zeile (Bello) self.assertEqual(reader[1][0], "Hund") self.assertEqual(reader[1][1], "Bello") finally: if os.path.exists(pfad): os.remove(pfad) class Test07GUIHeadless(unittest.TestCase): """Prüft die Benutzeroberfläche TierheimApp im Headless-Modus.""" def setUp(self): tk_mod = getattr(aufgabe, "tk", None) self.assertIsNotNone(tk_mod, "tk Modul in aufgabe.py fehlt!") self.root = tk_mod.Tk() self.heim = aufgabe.Tierheim("GUI Test Heim", max_kapazitaet=10) self.heim.tier_aufnehmen(aufgabe.Hund("Bello", 3, 14.5, rasse="Beagle", geimpft=True)) self.app = aufgabe.TierheimApp(self.root, self.heim) def test_23_gui_initialisierung_und_widgets(self): self.assertTrue(hasattr(self.app, "listbox_tiere")) self.assertTrue(hasattr(self.app, "label_status")) self.assertTrue(hasattr(self.app, "label_kapazitaet")) self.assertEqual(self.app.listbox_tiere.size(), 1) def test_24_gui_tier_aufnehmen_formular(self): # Formular mit neuem Hund befüllen self.app.var_art.set("Katze") self.app.var_name.set("Mimi") self.app.var_alter.set("1") self.app.var_gewicht.set("3.5") self.app.var_extra.set("ja") self.app.var_geimpft.set(False) # Aufnahmeklick auslösen self.app.tier_aufnehmen_klick() self.assertEqual(len(self.heim), 2) mimi = self.heim.finde_tier("Mimi") self.assertIsNotNone(mimi) self.assertIsInstance(mimi, aufgabe.Katze) def test_25_gui_validierungsfehler_abfangen(self): # Leerer Name -> Darf nicht abstürzen self.app.var_name.set("") try: self.app.tier_aufnehmen_klick() except Exception as e: self.fail(f"tier_aufnehmen_klick hat bei leerem Namen Exception nicht abgefangen: {e}") self.assertEqual(len(self.heim), 1, "Bestand darf sich bei Fehleingabe nicht verändern.") if __name__ == "__main__": unittest.main(verbosity=2)