====== LU07.L04 - Lange Funktion zerlegen ======
===== Schritt 0: Sicherheitsnetz =====
# main_test.py
import main
ERWARTET = (
"Bestellungen (4)\n"
"----------------\n"
"Meier: Maus = 75.0 CHF\n"
"Keller: Laptop = 1140.0 CHF\n"
"Meier: Tastatur = 152.0 CHF\n"
"Keller: Monitor = 199.5 CHF\n"
"----------------\n"
"Total: 1566.5 CHF"
)
def test_bericht_unveraendert():
assert main.bericht(main.BESTELLUNGEN) == ERWARTET
def test_rabatt_erst_ab_100():
knapp_darunter = [{"kunde": "x", "artikel": "a", "menge": 1, "preis": 99.0, "status": "offen"}]
assert "a = 99.0 CHF" in main.bericht(knapp_darunter)
def test_rabatt_ab_100_inklusive():
genau_100 = [{"kunde": "x", "artikel": "a", "menge": 1, "preis": 100.0, "status": "offen"}]
assert "a = 95.0 CHF" in main.bericht(genau_100)
def test_leere_liste():
assert main.bericht([]) == (
"Bestellungen (0)\n"
"----------------\n"
"----------------\n"
"Total: 0.0 CHF"
)
===== Ergebnis nach dem Refactoring =====
RABATT_AB_BETRAG = 100
RABATT_PROZENT = 5
STATUS_STORNIERT = "storniert"
def ist_gueltig(bestellung):
return bestellung["status"] != STATUS_STORNIERT
def zeilenwert(bestellung):
wert = bestellung["menge"] * bestellung["preis"]
if wert < RABATT_AB_BETRAG:
return wert
return wert * (1 - RABATT_PROZENT / 100)
def kundenname(bestellung):
return bestellung["kunde"].strip().lower().capitalize()
def zeile(bestellung):
artikel = bestellung["artikel"]
return f"{kundenname(bestellung)}: {artikel} = {round(zeilenwert(bestellung), 2)} CHF"
def bericht(bestellungen):
gueltige = [b for b in bestellungen if ist_gueltig(b)]
# Startwert 0.0: sonst liefert sum() bei leerer Liste ein int
total = round(sum((zeilenwert(b) for b in gueltige), 0.0), 2)
kopf = f"Bestellungen ({len(gueltige)})"
strich = "-" * len(kopf)
return "\n".join([kopf, strich] + [zeile(b) for b in gueltige] + [strich, f"Total: {total} CHF"])
===== Commit-Folge =====
refactor: Guard-Bedingung als ist_gueltig() extrahiert
refactor: Rabattgrenze und -satz als Konstanten benannt
refactor: Zeilenwert in zeilenwert() extrahiert
refactor: Namensaufbereitung in kundenname() extrahiert
refactor: Zeilenformatierung in zeile() extrahiert
refactor: Schleife durch Comprehension und sum() ersetzt
refactor: String-Verkettung durch f-Strings ersetzt
===== Die Falle in dieser Aufgabe =====
Der naheliegende Umbau
total = round(sum(zeilenwert(b) for b in gueltige), 2)
ist **falsch**. Bei einer leeren Liste liefert ''sum()'' den int ''0'', das Original arbeitet mit ''total = 0.0'' und liefert den float ''0.0''. Der Bericht endet dann mit ''Total: 0 CHF'' statt ''Total: 0.0 CHF''. Richtig ist der Startwert:
total = round(sum((zeilenwert(b) for b in gueltige), 0.0), 2)
Der Test ''test_leere_liste'' deckt genau das auf. Ohne ihn wäre die Änderung unbemerkt durchgegangen und später als kosmetischer Bug in der Rechnungsansicht wieder aufgetaucht. **Das ist der Grund, warum das Sicherheitsnetz vor dem ersten Umbau steht.**
===== Worauf es ankommt =====
* ''ist_gueltig'', ''zeilenwert'' und ''kundenname'' sind **pure** und einzeln testbar. Vorher war nichts davon prüfbar, ohne den ganzen Bericht zu erzeugen.
* Die Rabattregel steht an einer Stelle und heisst so, wie die Fachabteilung sie nennt.
* ''bericht'' liest sich jetzt wie eine Inhaltsangabe: filtern, summieren, formatieren.
* Die Tests ''test_rabatt_erst_ab_100'' und ''test_rabatt_ab_100_inklusive'' decken die Zeile ab, an der ein ''>'' statt ''>='' unbemerkt bliebe.
**Zweite Stolperstelle:** ''round()'' zu früh anwenden. Wer in ''zeilenwert'' rundet, bekommt bei anderen Datensätzen Rundungsdifferenzen im Total. Runden gehört an den Schluss.
----
{{tag>M323-LU07 M323-D1I}}
[[https://creativecommons.org/licenses/by-nc-sa/4.0/ch/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] (c) Kevin Maurizi