LU07.L04 - Lange Funktion zerlegen

# 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"
    )
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"])
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

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.

  • 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.


© Kevin Maurizi

  • modul/m323/learningunits/lu07/loesungen/langefunktion.txt
  • Zuletzt geändert: 2026/09/09 13:39
  • von admin