Debugging e testing
In questa pagina 5
In questa pagina 5
Tipi di errore
Definizione (tipi di errore)
Tipo Quando emerge Esempio sintattico prima dell'esecuzione (Python) / in compilazione (C) :mancante,;mancantedi tipo a run-time in Python, in compilazione in C (in parte) "a" + 1a run-time durante l'esecuzione divisione per zero, indice fuori intervallo, NULLdereferenziatologico mai segnalato: risultato sbagliato <invece di<=, accumulatore inizializzato male
Esempio. if x > 0 senza i due punti è un errore sintattico, "a" + 1 di tipo, 1 / 0 a run-time e < al posto di <= un errore logico.
In C molti errori a run-time non vengono segnalati affatto (comportamento indefinito): il programma può funzionare per caso e fallire altrove.
Metodo
- Riprodurre il problema con l'input più piccolo possibile.
- Leggere il messaggio: in Python il traceback dal fondo (tipo, riga, catena di chiamate, vedi Eccezioni in PythonEccezioni e traceback, eccezioni predefinite più comuni, try/except/else/finally, raise per segnalare errori, propagazione lungo le chiamate.Eccezioni in Python →); in C il primo errore del compilatore (i successivi sono spesso conseguenze).
- Formulare un'ipotesi e verificarla osservando lo stato (valori delle variabili) nel punto sospetto.
- Restringere: dimezzare il codice o l'input finché non si isola la causa.
- Correggere la causa, non il sintomo, e aggiungere un test che avrebbe trovato il bug.
Strumenti
Stampe di controllo
print(f"{i = }, {somma = }") # stampa nome e valorefprintf(stderr, "i=%d somma=%d\n", i, somma); /* stderr non è bufferizzato */Da togliere a lavoro finito.
Debugger
Permette di fermare il programma in un punto (breakpoint), eseguire una riga alla volta e ispezionare le variabili.
| Azione | VS Code | pdb (Python) |
gdb (C, compilare con -g) |
|---|---|---|---|
| avvio | F5 | python -m pdb prog.py o breakpoint() nel codice |
gdb ./prog, poi run |
| breakpoint | clic a sinistra del numero di riga | b 12 |
break 12 |
| riga successiva | F10 | n |
next |
| entra nella funzione | F11 | s |
step |
| continua | F5 | c |
continue |
| valore | pannello Variabili | p x |
print x |
| stack delle chiamate | pannello Call Stack | w |
backtrace |
gdb su un programma che va in segmentation fault mostra, con backtrace, la riga esatta del crash.
assert
Definizione (assert).
assert condizione, "messaggio"interrompe il programma se la condizione è falsa. Serve a verificare ipotesi interne (precondizioni, invarianti di ciclo), non a validare l'input dell'utente.def media(v): assert len(v) > 0, "lista vuota" return sum(v) / len(v)
Esempio. Con def media(v): assert len(v) > 0, "lista vuota", la chiamata media([]) solleva AssertionError: lista vuota.
#include <assert.h>
assert(n > 0); /* disattivato compilando con -DNDEBUG */Errori di memoria in C
gcc -Wall -Wextra -g -fsanitize=address,undefined prog.c: a run-time segnala accessi fuori dai limiti, uso dopofree, overflow di interi con segno.valgrind --leak-check=full ./prog: memoria non liberata e letture di memoria non inizializzata.
Testing
Definizione (test). Un test esegue il codice su un input con risultato noto e confronta. I test mostrano la presenza di errori, non ne dimostrano l'assenza: vanno scelti bene.
Esempio. assert ricerca_binaria([1, 3, 5, 7], 4) == -1 è un test: passa in silenzio se la funzione restituisce -1 e solleva AssertionError altrimenti.
Scegliere i casi
- Casi normali: qualche input tipico.
- Casi limite: sequenza vuota, un solo elemento, primo/ultimo elemento, valori uguali, zero, negativi, massimo intero in C.
- Partizioni: dividere gli input in classi che il codice tratta allo stesso modo (es. per la ricerca: elemento presente all'inizio, in mezzo, alla fine, assente) e testare almeno un caso per classe e i confini tra le classi.
- Input non validi: verificare che venga sollevata l'eccezione prevista o restituito il codice d'errore.
Test in Python
Con semplici assert in una funzione di test:
def test_ricerca_binaria():
v = [1, 3, 5, 7]
assert ricerca_binaria(v, 1) == 0 # primo
assert ricerca_binaria(v, 7) == 3 # ultimo
assert ricerca_binaria(v, 4) == -1 # assente, in mezzo
assert ricerca_binaria([], 1) == -1 # vuota
assert ricerca_binaria([5], 5) == 0 # un elemento
if __name__ == "__main__":
test_ricerca_binaria()
print("tutti i test superati")Con il modulo unittest della libreria standard:
import unittest
from algoritmi import ricerca_binaria, media # modulo con il codice da testare
class TestRicerca(unittest.TestCase):
def test_presente(self):
self.assertEqual(ricerca_binaria([1, 3, 5], 3), 1)
def test_assente(self):
self.assertEqual(ricerca_binaria([1, 3, 5], 2), -1)
def test_media_vuota(self):
with self.assertRaises(ValueError):
media([])
if __name__ == "__main__":
unittest.main()Definizione (doctest). Con
doctestgli esempi nella docstring diventano test (python -m doctest modulo.py -v):def quadrato(x): """ >>> quadrato(3) 9 >>> quadrato(-2) 4 """ return x * x
Esempio. python -m doctest modulo.py -v esegue quadrato(3) e quadrato(-2) e verifica che restituiscano 9 e 4.
Test in C
Senza librerie esterne, una funzione di test con assert chiamata da main:
#include <assert.h>
void test_lunghezza(void) {
assert(lunghezza("") == 0);
assert(lunghezza("a") == 1);
assert(lunghezza("ciao") == 4);
}
int main(void) {
test_lunghezza();
printf("ok\n");
return 0;
}Quando testare
Scrivere i test insieme al codice (o prima), rieseguirli dopo ogni modifica (test di regressione). Funzioni piccole che restituiscono valori invece di stamparli (Funzioni in Pythondef e return, parametri posizionali, con nome e con valore predefinito, *args e **kwargs; passaggio per riferimento a oggetto; ambito delle variabili LEGB; docstring, type hint, lambda.Funzioni in Python →) sono molto più facili da testare.
Errori tipici
- Correggere a tentativi senza un'ipotesi.
- Testare solo il caso "medio" e mai sequenze vuote o estremi.
- Usare
assertper controllare l'input dell'utente (in C sparisce con-DNDEBUG, in Python conpython -O). - Lasciare stampe di debug nel codice consegnato.
Versione ripasso
Tipi di errore
- Sintattico: prima dell'esecuzione (Python) / in compilazione (C).
- Di tipo: a run-time in Python, in compilazione in C (in parte).
- A run-time: durante l'esecuzione (es. divisione per zero,
NULLdereferenziato). In C causano comportamento indefinito. - Logico: mai segnalato, risultato sbagliato.
Metodo
- Riprodurre con l'input più piccolo possibile.
- Leggere il messaggio: traceback dal fondo in PythonEccezioni e traceback, eccezioni predefinite più comuni, try/except/else/finally, raise per segnalare errori, propagazione lungo le chiamate.Eccezioni in Python →, primo errore in C.
- Formulare un'ipotesi e verificare lo stato delle variabili.
- Restringere dimezzando codice o input.
- Correggere la causa (non il sintomo) e aggiungere un test.
Strumenti
Debugger
| Azione | VS Code | pdb (Python) |
gdb (C, -g) |
|---|---|---|---|
| avvio | F5 | python -m pdb prog.py |
gdb ./prog, poi run |
| breakpoint | clic a sinistra | b 12 |
break 12 |
| riga successiva | F10 | n |
next |
| entra in funzione | F11 | s |
step |
| continua | F5 | c |
continue |
| valore | Variabili | p x |
print x |
| call stack | Call Stack | w |
backtrace |
assert
assert condizione, "messaggio" interrompe il programma se falsa. Verifica ipotesi interne (precondizioni, invarianti), non l'input utente. In C si disattiva con -DNDEBUG.
Errori di memoria in C
gcc -Wall -Wextra -g -fsanitize=address,undefined prog.c: accessi fuori limite, uso dopofree, overflow.valgrind --leak-check=full ./prog: memory leak e letture non inizializzate. Vedi Gestione della memoria in CSegmenti di memoria di un processo (codice, dati statici, stack, heap); durata delle variabili; allocazione dinamica con malloc, calloc, realloc e free; errori classici: memory leak, dangling pointer, double free, buffer overflow; strumenti di controllo.Gestione della memoria in C →.
Testing
I test mostrano la presenza di errori, non l'assenza.
Scegliere i casi
- Casi normali, casi limite (vuoti, singoli elementi, estremi, massimi), partizioni (classi di equivalenza), input non validi (eccezioni/codici d'errore).
Test in Python e C
unittest:class TestX(unittest.TestCase):con metodiself.assertEqual,self.assertRaises.doctest: test direttamente nelle docstring (>>>).- In C: funzioni di test con
assertchiamate damain.
Errori tipici
- Correggere a tentativi senza ipotesi.
- Testare solo il caso medio dimenticando i casi limite.
- Usare
assertper validare l'input utente. - Lasciare stampe di debug nel codice.