Technikum Klasa I 45 minut PP: II.1 + I.3 | s. 342-343

Lekcja 27: Testowanie poprawnosci programow dla roznych danych

Przypadki testowe, dane brzegowe, assert, debugowanie kodu

📋 Podstawa programowa: II.1+I.3
assertdebugowanietestowanietesty
00:00
Wprowadzenie
5 min
00:05
Teoria
15 min
00:20
Cwiczenia
15 min
00:35
Podsumowanie
10 min
📚

Teoria

Dlaczego testujemy programy?

Kazdy program moze zawierac bledy (ang. bugs). Testowanie to proces sprawdzania, czy program dziala poprawnie dla roznych danych wejsciowych. Nawet jesli program kompiluje sie i uruchamia bez bledow, moze zwracac nieprawidlowe wyniki dla niektorych danych.

Zasada testowania: Testowanie moze wykazac obecnosc bledow, ale nigdy nie moze udowodnic ich braku. Dlatego wazne jest testowanie dla jak najwiekszej liczby roznych przypadkow, w tym danych brzegowych i nietypowych.

Rodzaje przypadkow testowych

  • Dane typowe - standardowe dane, dla ktorych program powinien dzialac poprawnie (np. lista kilku liczb do posortowania)
  • Dane brzegowe (edge cases) - skrajne wartosci: pusta lista, jeden element, zero, liczby ujemne, bardzo duze liczby
  • Dane niepoprawne - dane, ktorych program nie powinien akceptowac (np. tekst zamiast liczby)
  • Dane specjalne - powtarzajace sie elementy, juz posortowane dane, dane posortowane odwrotnie

Instrukcja assert

Instrukcja assert w Pythonie sprawdza, czy warunek jest prawdziwy. Jesli nie - program przerywa dzialanie z bledem AssertionError. To najprostszy sposob testowania:

# Funkcja do przetestowania
def silnia(n):
    if n < 0:
        return None
    wynik = 1
    for i in range(1, n + 1):
        wynik *= i
    return wynik

# Testy z uzyciem assert
assert silnia(0) == 1, "Silnia z 0 powinna wynosic 1"
assert silnia(1) == 1, "Silnia z 1 powinna wynosic 1"
assert silnia(5) == 120, "Silnia z 5 powinna wynosic 120"
assert silnia(10) == 3628800, "Silnia z 10 powinna wynosic 3628800"
assert silnia(-1) == None, "Silnia z liczby ujemnej powinna zwrocic None"

print("Wszystkie testy przeszly!")

Pisanie dobrych testow

def jest_palindromem(tekst):
    tekst = tekst.lower().replace(" ", "")
    return tekst == tekst[::-1]

# Testy - rozne przypadki
assert jest_palindromem("kajak") == True      # typowy palindrom
assert jest_palindromem("Kajak") == True      # wielkie litery
assert jest_palindromem("python") == False    # nie-palindrom
assert jest_palindromem("") == True           # pusty tekst (brzegowy)
assert jest_palindromem("a") == True          # jeden znak (brzegowy)
assert jest_palindromem("aa") == True         # dwa takie same znaki
assert jest_palindromem("ab") == False        # dwa rozne znaki
assert jest_palindromem("A ba") == True       # spacje w srodku

print("Wszystkie testy palindromu OK!")
Wskazowka: Dobre testy powinny sprawdzac: (1) typowe dane wejsciowe, (2) graniczne wartosci (0, 1, puste dane), (3) dane niepoprawne, (4) wyniki oczekiwane i nieoczekiwane. Kazdy test powinien sprawdzac JEDEN konkretny przypadek.

Debugowanie - print vs debugger

Gdy program nie dziala poprawnie, musimy znalezc blad. Sa dwie glowne metody:

Metoda 1: Debugowanie przez print (print debugging)

def znajdz_max(lista):
    if len(lista) == 0:
        return None
    maks = lista[0]
    print(f"Start: maks = {maks}")  # debug
    for i in range(1, len(lista)):
        print(f"  Porownuje {maks} z {lista[i]}")  # debug
        if lista[i] > maks:
            maks = lista[i]
            print(f"  Nowe maks = {maks}")  # debug
    return maks

wynik = znajdz_max([3, 7, 2, 9, 1])
print(f"Wynik: {wynik}")

Metoda 2: Debugger w srodowisku IDE

Wieksznosc srodowisk (PyCharm, VS Code, IDLE) posiada wbudowany debugger, ktory pozwala:

  • Breakpoint - zatrzymanie programu w wybranym miejscu
  • Step over - wykonanie jednej linii kodu
  • Step into - wejscie do wnetrza funkcji
  • Watch - obserwowanie wartosci zmiennych w czasie rzeczywistym

Programowanie defensywne

Programowanie defensywne polega na zabezpieczaniu kodu przed niepoprawnymi danymi wejsciowymi:

def dzielenie(a, b):
    """Dzieli a przez b z zabezpieczeniem."""
    if not isinstance(a, (int, float)):
        raise TypeError("Argument a musi byc liczba")
    if not isinstance(b, (int, float)):
        raise TypeError("Argument b musi byc liczba")
    if b == 0:
        raise ValueError("Nie mozna dzielic przez zero!")
    return a / b

# Testy
assert dzielenie(10, 2) == 5.0
assert dzielenie(7, 3) == 7/3
assert dzielenie(-10, 2) == -5.0

# Te wywolania powinny rzucic bledy:
try:
    dzielenie(10, 0)
except ValueError as e:
    print(f"Blad: {e}")  # Blad: Nie mozna dzielic przez zero!

try:
    dzielenie("abc", 2)
except TypeError as e:
    print(f"Blad: {e}")  # Blad: Argument a musi byc liczba
✏️

Zadania

Latwe

Zadanie 1: Napisz testy dla funkcji

Dana jest funkcja srednia(lista), ktora oblicza srednia arytmetyczna listy liczb. Napisz co najmniej 6 testow z uzyciem assert, obejmujacych dane typowe, brzegowe i specjalne.

Pokaz przykladowe rozwiazanie
def srednia(lista):
    if len(lista) == 0:
        return 0
    return sum(lista) / len(lista)

# Testy
assert srednia([1, 2, 3, 4, 5]) == 3.0, "Srednia z 1-5 to 3"
assert srednia([10]) == 10.0, "Jeden element"
assert srednia([0, 0, 0]) == 0.0, "Same zera"
assert srednia([-5, 5]) == 0.0, "Liczby ujemne i dodatnie"
assert srednia([]) == 0, "Pusta lista"
assert srednia([1000000, 2000000]) == 1500000.0, "Duze liczby"
assert srednia([1, 1, 1, 1]) == 1.0, "Identyczne elementy"
assert abs(srednia([1, 2]) - 1.5) < 0.001, "Wynik ulamkowy"

print("Wszystkie testy sredniej przeszly!")
Srednie

Zadanie 2: Znajdz bledy w kodzie

Ponizsze funkcje zawieraja bledy. Uzyj testow i debugowania, aby je znalezc i naprawic:

# Funkcja A - powinna zwracac True jesli n jest parzyste
def jest_parzysta(n):
    return n % 2 == 1  # BUG!

# Funkcja B - powinna zwracac liste bez duplikatow
def usun_duplikaty(lista):
    wynik = []
    for el in lista:
        if el in wynik:  # BUG!
            wynik.append(el)
    return wynik

# Funkcja C - powinna odwracac tekst
def odwroc(tekst):
    wynik = ""
    for i in range(len(tekst)):  # BUG!
        wynik += tekst[i]
    return wynik
Pokaz przykladowe rozwiazanie
# Naprawiona funkcja A
def jest_parzysta(n):
    return n % 2 == 0  # zmieniono 1 na 0

# Naprawiona funkcja B
def usun_duplikaty(lista):
    wynik = []
    for el in lista:
        if el not in wynik:  # dodano "not"
            wynik.append(el)
    return wynik

# Naprawiona funkcja C
def odwroc(tekst):
    wynik = ""
    for i in range(len(tekst) - 1, -1, -1):  # iteracja od konca
        wynik += tekst[i]
    return wynik

# Testy naprawionych funkcji
assert jest_parzysta(4) == True
assert jest_parzysta(7) == False
assert jest_parzysta(0) == True

assert usun_duplikaty([1, 2, 2, 3, 3, 3]) == [1, 2, 3]
assert usun_duplikaty([]) == []
assert usun_duplikaty([1]) == [1]

assert odwroc("abc") == "cba"
assert odwroc("") == ""
assert odwroc("a") == "a"

print("Wszystkie naprawione funkcje dzialaja!")
Srednie

Zadanie 3: Programowanie defensywne

Napisz funkcje bezpieczna_potega(podstawa, wykladnik), ktora oblicza potege z pelnym zabezpieczeniem: sprawdzanie typow, walidacja zakresu (wykladnik >= 0, wykladnik <= 1000), obsluga wyjatkow. Dodaj testy.

Pokaz przykladowe rozwiazanie
def bezpieczna_potega(podstawa, wykladnik):
    """Oblicza podstawa^wykladnik z walidacja danych."""
    # Sprawdzenie typow
    if not isinstance(podstawa, (int, float)):
        raise TypeError(f"Podstawa musi byc liczba, otrzymano: {type(podstawa)}")
    if not isinstance(wykladnik, int):
        raise TypeError(f"Wykladnik musi byc liczba calkowita, otrzymano: {type(wykladnik)}")

    # Sprawdzenie zakresu
    if wykladnik < 0:
        raise ValueError("Wykladnik nie moze byc ujemny")
    if wykladnik > 1000:
        raise ValueError("Wykladnik zbyt duzy (max 1000)")

    return podstawa ** wykladnik

# Testy poprawnych wywolan
assert bezpieczna_potega(2, 0) == 1
assert bezpieczna_potega(2, 10) == 1024
assert bezpieczna_potega(0, 5) == 0
assert bezpieczna_potega(3, 3) == 27
assert bezpieczna_potega(-2, 3) == -8
assert bezpieczna_potega(1.5, 2) == 2.25

# Testy blednych danych
import sys

try:
    bezpieczna_potega("abc", 2)
    assert False, "Powinien rzucic TypeError"
except TypeError:
    pass  # OK

try:
    bezpieczna_potega(2, -1)
    assert False, "Powinien rzucic ValueError"
except ValueError:
    pass  # OK

try:
    bezpieczna_potega(2, 1001)
    assert False, "Powinien rzucic ValueError"
except ValueError:
    pass  # OK

print("Wszystkie testy bezpiecznej potegi przeszly!")
Trudne

Zadanie 4: Kompleksowe testowanie sortowania

Napisz zestaw testow dla funkcji sortowania babelkowego. Przetestuj: pusta lista, jeden element, juz posortowana, odwrotnie posortowana, duplikaty, liczby ujemne, duza lista losowa (porownaj z wbudowanym sorted()).

Pokaz przykladowe rozwiazanie
import random

def sortuj_babelkowo(lista):
    lista = lista.copy()
    n = len(lista)
    for i in range(n - 1):
        for j in range(n - 1 - i):
            if lista[j] > lista[j + 1]:
                lista[j], lista[j + 1] = lista[j + 1], lista[j]
    return lista

# Test 1: Pusta lista
assert sortuj_babelkowo([]) == [], "Pusta lista"

# Test 2: Jeden element
assert sortuj_babelkowo([42]) == [42], "Jeden element"

# Test 3: Juz posortowana
assert sortuj_babelkowo([1, 2, 3, 4, 5]) == [1, 2, 3, 4, 5], "Posortowana"

# Test 4: Odwrotnie posortowana
assert sortuj_babelkowo([5, 4, 3, 2, 1]) == [1, 2, 3, 4, 5], "Odwrotnie"

# Test 5: Z duplikatami
assert sortuj_babelkowo([3, 1, 3, 2, 1]) == [1, 1, 2, 3, 3], "Duplikaty"

# Test 6: Liczby ujemne
assert sortuj_babelkowo([-3, 5, -1, 0, 2]) == [-3, -1, 0, 2, 5], "Ujemne"

# Test 7: Dwa elementy
assert sortuj_babelkowo([2, 1]) == [1, 2], "Dwa elementy"

# Test 8: Identyczne elementy
assert sortuj_babelkowo([7, 7, 7]) == [7, 7, 7], "Identyczne"

# Test 9: Duza lista losowa
losowa = [random.randint(-1000, 1000) for _ in range(100)]
assert sortuj_babelkowo(losowa) == sorted(losowa), "Losowa lista 100 elementow"

# Test 10: Porownanie z sorted() na wielu probkach
for _ in range(20):
    test = [random.randint(-50, 50) for _ in range(random.randint(0, 30))]
    assert sortuj_babelkowo(test) == sorted(test), f"Losowy test: {test}"

print("Wszystkie testy sortowania przeszly! (10+ przypadkow)")
🎥

Materialy wideo

Jak Zostać TESTEREM OPROGRAMOWANIA | Wybór Techniki Testowania | ▶strefakursow.pl◀ #tester
strefakursow.pl
Listy rozwijane || Excel #2
Info Cube
🎧

Podcasty

✔️

Quiz - sprawdz sie!

📜

Podstawa programowa

← Lekcja 26: Ciag Fibonacciego i inne ciagi Lekcja 28: Netykieta i ochrona danych osobowych →