Testowanie i debugowanie

🎯 Po co Ci to?

Przykra wiadomość na start: Twoje programy będą miały błędy. Wszystkie. Zawsze. Programy zawodowców też — różnica polega na tym, że zawodowcy błędy zakładają i mają rzemiosło ich znajdowania, zanim znajdzie je użytkownik (albo komisja maturalna). To rzemiosło ma dwie części: testowanie (jak sprawdzać, żeby błędy wyszły na jaw) i debugowanie (jak znaleźć przyczynę, gdy już wyszły). Dziś przestaniesz się błędów wstydzić, a zaczniesz na nie polować.

✅ Czego się nauczysz

Po tej jednostce potrafisz:

  • rozróżnić trzy rodzaje błędów: składni, wykonania i logiczne — i czytać komunikaty Pythona;
  • dobrać dane testowe, które naprawdę sprawdzają program (typowe + brzegowe + błędne);
  • tropić błędy logiczne metodą śledzenia i wstawek print.

📘 Wyjaśnienie

Trzy rodzaje błędów. Każdy błąd programisty należy do jednej z trzech rodzin — a każda rodzina inaczej się objawia i inaczej się ją leczy:

  • Błąd składni (syntax error): program w ogóle nie rusza — zapomniany dwukropek, nawias bez pary, złe wcięcie. Python wskazuje miejsce (lub okolicę — czasem prawdziwa usterka jest linijkę wyżej, np. niedomknięty nawias). Najłatwiejszy: głośny i natychmiastowy.
  • Błąd wykonania (runtime error): program rusza i pada w trakcie — dzielenie przez zero, int("abc"), IndexError poza listą. Python wypisuje wtedy traceback: raport z miejsca wypadku. Czytaj go od dołu: ostatnia linia mówi co się stało, przedostatnie — gdzie.
  • Błąd logiczny: program działa, kończy się grzecznie… i liczy źle. Żadnego komunikatu, żadnego ostrzeżenia. Najgroźniejszy z całej rodziny — bo wykrywa go dopiero porównanie wyniku z oczekiwaniem. Czyli: test.

Testowanie. Nie „uruchomię i zobaczę, czy działa" — to nie jest test, to rytuał odpędzania złych mocy. Test to przewidzenie wyniku przed uruchomieniem i porównanie z tym, co wyszło. Dane testowe dobiera się z trzech koszyków (dwa pierwsze znasz z działu 1!):

  1. Typowe — zwykły przypadek: średnia z ocen [3, 4, 5] powinna wyjść 4.0.
  2. Brzegowe — krawędzie zakresów: lista jednoelementowa, pusta, same zera, wartość dokładnie na progu (punkty == 50!), największa dopuszczalna.
  3. Błędne — dane spoza specyfikacji: tekst zamiast liczby, wartość ujemna tam, gdzie ma nie być. Sprawdzasz, czy program pada z wdziękiem (komunikat), a nie z hukiem (traceback) — albo świadomie decydujesz, że obsługa błędnych danych jest poza umową (i wtedy specyfikacja ma to mówić!).

Debugowanie. Test mówi „jest źle"; debugowanie odpowiada „gdzie i czemu". Metoda podstawowa to Twoje stare dobre śledzenie (dział 1) — tyle że przy dłuższych programach ręczna tabelka bywa za wolna. Wtedy każesz programowi prowadzić ją za Ciebie, wstawiając tymczasowe print w newralgiczne miejsca:

def srednia_dodatnich(lista):
    suma = 0
    ile = 0
    for x in lista:
        print("DEBUG: x =", x, "suma =", suma, "ile =", ile)   # podgląd wnętrza
        if x > 0:
            suma = suma + x
            ile = ile + 1
    return suma / ile

Uruchamiasz, patrzysz na przebieg wartości i w którymś wierszu widzisz rozjazd między tym, co jest, a tym, co powinno być — to tam mieszka błąd. Po naprawie wstawki print usuwasz. (Środowiska programistyczne mają do tego wygodniejsze narzędzie — debugger; poznasz go w jednostce 3.8. Ale metoda print działa wszędzie i uczy najważniejszego: formułowania oczekiwań.)

I jeszcze nawyk, który odróżnia rzemieślnika od partacza: po znalezieniu błędu nie poprawiaj wyniku — popraw przyczynę, a potem uruchom wszystkie testy jeszcze raz. Naprawy uwielbiają psuć coś obok.

🧮 Prześledź

Funkcja srednia_dodatnich z przykładu wyżej ma błąd, który ujawni się dla pewnych danych. Prześledź ją dla [-2, -5] — co się stanie w ostatniej linijce?

Sprawdź odpowiedź

Żaden element nie przejdzie warunku, więc ile zostanie zerem — a suma / ile to dzielenie przez zero: błąd wykonania ZeroDivisionError. Dane „same ujemne" to klasyczny brzeg (pusta praca pętli — jak pusta lista). Naprawa to decyzja projektowa: zwrócić 0? zwrócić None? wypisać komunikat? Wybór należy do specyfikacji — ale jakiś wybór musi zapaść, bo obecny program po prostu pada.

🐞 Znajdź błąd

Program ma liczyć, ile lat kapitał się podwaja — ale zawodowiec od razu widzi w nim dwa błędy: jeden wykonania (czasem), jeden logiczny (zawsze). Znajdź oba.

kapital = float(input("Kapitał: "))
procent = float(input("Oprocentowanie w %: "))
lata = 0
while kapital < kapital * 2:
    kapital = kapital + kapital * procent / 100
    lata = lata + 1
print("Podwojenie po", lata, "latach")
Sprawdź odpowiedź

Błąd logiczny: warunek kapital < kapital * 2 jest prawdziwy dla każdego dodatniego kapitału — obie strony rosną razem, więc pętla nigdy nie skończy się z powodu podwojenia. Cel („dwukrotność kapitału początkowego") trzeba zamrozić przed pętlą: cel = kapital * 2, warunek kapital < cel. Błąd wykonania/wieczna pętla: dla procent = 0 (albo ujemnego) kapitał nie rośnie i nawet poprawiony warunek nigdy nie zgaśnie — dane błędne trzeba odrzucić przed pętlą. Bonus dla czujnych: dla kapital = 0 pętla… nie wykona się wcale (0 < 0 fałszywe) i program ogłosi podwojenie po 0 latach — brzeg, o którym mało kto pomyśli.

⚠️ Uwaga, pułapka

„U mnie działa" to najsłynniejsze ostatnie słowa programisty. Program przetestowany tylko na danych typowych jest jak most testowany tylko przy bezwietrznej pogodzie. Jeżeli po Twoim programie ktokolwiek (nauczyciel? komisja? kolega?) będzie wpisywał własne dane — załóż, że wpisze najgorsze możliwe. Bo wpisze.

🛠️ Teraz Ty

Weź swój program-zgadywankę z jednostki 3.3 i zaplanuj dla niego na kartce komplet testów: po dwa z każdego koszyka (typowe, brzegowe, błędne). Wykonaj je. Przynajmniej jeden powinien coś znaleźć — np. co się dzieje, gdy użytkownik wpisze „dużo" zamiast liczby, albo zgadnie za pierwszym razem. Napraw i przetestuj wszystko ponownie.

📐 Definicje tej lekcji

  • Błąd składni / wykonania / logiczny — nie rusza / pada w trakcie / liczy źle po cichu.
  • Traceback — raport Pythona z błędu wykonania; czytany od dołu wskazuje co i gdzie.
  • Dane testowe — typowe + brzegowe + błędne; test = przewidzenie wyniku przed uruchomieniem.
  • Debugowanie — szukanie przyczyny błędu; podstawowa technika: podgląd stanu zmiennych (śledzenie, wstawki print).

📌 Najważniejsze w pigułce

  • Błędy są normą; rzemiosło polega na znajdowaniu ich systematycznie, nie na unikaniu wstydu.
  • Najgroźniejszy jest błąd logiczny — wykrywa go tylko porównanie z przewidzianym wynikiem.
  • Po każdej naprawie — wszystkie testy od nowa; poprawiaj przyczyny, nie objawy.

🎒 Zadania

  1. Zaklasyfikuj błędy do trzech rodzin: (a) print("wynik:" wynik), (b) średnia z pustej listy, (c) pole koła liczone jako $2\pi r$, (d) IndexError przy lista[len(lista)], (e) if x = 5:.
Wskazówka i odpowiedź

(a) składni — brak przecinka; nie ruszy. (b) wykonania — dzielenie przez zero. (c) logiczny — program elegancko poda obwód zamiast pola; wykryje go tylko test z ręcznie policzonym wynikiem. (d) wykonania — ostatni indeks to len - 1 (kłania się 3.4). (e) składni — przypisanie w warunku; Python odmówi startu (i słusznie: pomyłka =/== bywa w innych językach cichym koszmarem).

  1. Funkcja przestepny(rok) z jednostki 3.5 wygląda na skończoną. Zaproponuj minimalny zestaw lat testowych, który sprawdza każdą ścieżkę przez funkcję, i podaj oczekiwane wyniki.
Wskazówka i odpowiedź

Cztery ścieżki = minimum cztery testy: 2000 (dzieli się przez 400 → True), 1900 (przez 100, nie 400 → False), 2024 (przez 4, nie 100 → True), 2023 (nic → False). Zestaw pokrywający wszystkie gałęzie to po fachowemu „pokrycie ścieżek" — przy kaskadach warunków licz ścieżki, nie przykłady „na oko".

  1. Kolega debuguje program 30 minut, zmieniając losowo znaki „może zadziała". Opisz mu w punktach metodę, którą poznałeś — od objawu do naprawy.
Wskazówka i odpowiedź

(1) Zreprodukuj: znajdź konkretne dane, na których jest źle. (2) Przewidź: rozpisz, jakie wartości powinny być w kluczowych miejscach. (3) Podejrzyj: wstaw print/tabelkę śledzenia i znajdź pierwszy rozjazd „jest vs powinno". (4) Zrozum: wyjaśnij, czemu tam się rozjeżdża — zmiana bez zrozumienia to loteria. (5) Napraw przyczynę. (6) Przetestuj wszystko od nowa, także to, co działało. Losowe zmienianie znaków ma swoją nazwę: programowanie przez zaklinanie — i statystykę skuteczności bliską zeru.

🔍 Sprawdź, czy umiesz

  • Przeczytać traceback od dołu i wskazać winną linijkę.
  • Zaprojektować testy z trzech koszyków dla dowolnego swojego programu.
  • Wytropić błąd logiczny wstawkami print i wyjaśnić przyczynę, zanim naprawisz.

Ucz się tej jednostki z asystentem