Lezione: collaudo di funzioni
Le funzioni e i programmi che scriviamo andrebbero collaudati. Collaudare una funzione o un programma vuol dire verificarne il corretto funzionamento con quanti più dati possibile. Ovviamente non siamo in grado di provare tutti i possibili input, ma quanto meno andrebbe verificato il corretto funzionamento nei casi principali. Ad esempio, per un programma che prende 3 numeri e restituisce il minimo, andrebbero provati i casi in cui il minimo è il primo, il secondo o il terzo numero della serie, e anche i casi in cui due o più numeri in input sono uguali. Questo non ci da la certezza assoluta che il programma sia corretto, ma quanto meno ci instilla un po' di fiducia. Di questo, in realtà, abbiamo già parlato nella lezione sul collaudo di programmi.
Vedremo adesso più in dettaglio come collaudare una funzione (il collaudo di programmi è in generale più difficile). Nelle precedenti lezioni abbiamo spesso accompagnato le funzioni che abbiamo scritto con un programma principale (detto programma driver) che richiamava la funzione con input noti, e poi noi esseri umani controllavamo manualmente che il risultato fosse quello corretto. In alternativa, abbiamo talvolta usato un notebook per scrivere il codice della nostra funzione, che poi richiamavamo dal notebook stesso controllando i risultati. Entrambi questi meccanismi sono inerentemente manuali e tediosi. Se ci accorgiamo di un bug in una funzione e la modifichiamo, dobbiamo ripetere i test manualmente da capo, col rischio di dimenticarci qualche test importante. Per questo, la maggior parte dei linguaggi di programmazione e degli ambienti di sviluppo prevedono dei meccanismi appositi per il collaudo del software. Noi vedremo il meccanismo per gli unit test (collaudo di moduli) che possiamo utilizzare su Python con VSCode. Perché il supporto dei test con VSCode funzioni correttamente, è necessario che su VSCode non venga aperto il singolo file ma una cartella, dove il programmatore inserirà sia il codice delle funzioni, che i programmi di test (usare per questo la voce del menù File → Open Folder). Questa è comunque una buona pratica per lavorare con VSCode, perché è possibile manipolare facilmente i file direttamente dall'ambiente di sviluppo. È quindi raccomandabile anche se non si ha intenzione di fare alcun test.
Supponendo quindi di aver aperto una cartella, e creiamo una file di esempio con una funzione da testare. Chiamiamo il file, ad esempio, miomodulo.py e mettiamo all'interno il codice della funzione multiply che abbiamo visto nella lezione su come operare sul posto con le liste.
Vogliamo adesso collaudare questo metodo. Per esempio, diciamo di voler controllare che:
- se moltiplico la lista [1,2,3] per 5, ottengo [5, 10, 15]
- se moltiplico la lista [1] per 0, ottengo [0]
- se moltiplico la lista vuota per 3, ottengo la lista vuota
Creiamo un altro file che chiamiamo test_multiply.py. Il nome esatto del file non importa, quello che importa è che inizi con "test_". Dentro il file, scriviamo due funzioni test_multiply_1 e test_mulyiply_2 col seguente codice:
Ancora una volta, il nome esatto delle funzioni non importa, quello che importa è che inizino con test_. Dentro il codice delle funzioni di test, l'unica cosa nuova è l'istruzione assert. Questa istruzione controlla che la condizione indicata dopo sia valida, e se non lo è genera un errore. Sebbene sia utilizzabile anche in programmi standard, il suo utilizzo principale è nei test. Si noti anche l'uso della istruzione from...import per importare la funzione multiply definita nel file miomodulo.py. Infine, notare che sarebbe stato possibile anche scrivere tutte le assert in una singola funzione (o separare le tre assert in tre funzioni).
A questo punto, per lanciare i test, dobbiamo prima configurare VSCode in modo opportuno. Prima di tutto, cliccare nella barra delle icone sull'icona (Testing). Se non abbiamo precedentemente abilitato i test, possiamo a questo punto premere il pulsante "Configure Python Tests" che appare nella bara laterale sinistra (tra la barra delle icone e lo spazio dove si edita il codice).
Vi viene chiesto (in alto nella finestra di VSCode), di scegliere il framework da utilizzare per i test: selezionare pytest.
Vii viene quindi chiesto quale cartella contiene i file di test: selezionate Root directory.
A questo punto, il pulsante "Configure Python Tests" scompare e al suo posto appare la lista di test che il sistema trova nella nostra cartella. Una volta espansa, la lista dei test appare come segue:
Muovendo il mouse sopra uno delle tre linee, compare una serie di pulsanti a destra. Cliccando il pulsante "Run Test" si può lanciare un test (o un gruppo di test):
Ovviamente i test hanno successo (perché la funzione reverse che abbiamo scritto è corretta), e compaiono dei segni di spunta verdi accanto alle righe con i test.
Proviamo però ad inserire un bug nella funzione reverse. Diciamo che abbiamo sbagliato a scrivere i parametri della funzione range, e invece di range(len(l)) mettiamo range(len(l)-1). Se rieseguiamo i test, otteniamo:
Si noti che test_multiply_1 ha avuto comunque successo, ma test_multiply_2 no! Di conseguenza la riga test_multiply.py (che raggruppa tutti i test presenti nel file test_multiply.py) e la riga collaudo (la cartella che ho aperto su VSCode e che raggruppa tutti i file di test) vengono segnalati anch'esse come errate.
Inoltre, sul codice sorgente di test_multiply_2 si hanno più dettagli sul motivo per cui il test è fallito:
Da questa pagina si evince che il problema è sulla riga "assert multiply([1], 0) == [0]". Poiché multiply adesso è bacata, l'esecuzione di multiply([1], 0) ha dato la lista [None] come risultato, per cui il test è diventato equivalente a "assert [None] == 1" che è ovviamente falso.
ATTENZIONE: per utilizzare pytest, è necessario che la libreria corrispondente sia installata. Sui PC dell'aula informatica questo è stato già fatto, nel vostro PC personale potete installarla seguendo le istruzioni del documento "Installazione dell'ambiente di sviluppo".
Test di funzioni che operano sul posto
Cosa cambia se vogliamo collaudare la funzione multiply_inplace invece di multiply ?
Aggiungiamo il codice di multiply_inplace (che trovate sempre nella lezione "operare sul posto con le liste") al file miomodulo.py. Aggiungiamo un corrispondente file contenente i test, chiamamolo test_multiply_inplace.py, con il seguente contenuto:
Si noti che i test sono leggermente più complicati. Siccome la funzione multiply_inplace non restituisce nulla, adesso ogni test richiede più linee di codice:
- nella prima riga creiamo una lista l con il valore in input per la funzione multiply_inplace;
- nella seconda riga chiamiamo la funzione;
- nella terza riga controlliamo se la lista l assume adesso il valore desiderato.