Collaudo di funzioni con pytest

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 del 17/10/2023.

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, prova.py e mettiamo all'interno il codice della funzione reverse che abbiamo visto nella lezione del 16 novembre.


Vogliamo adesso collaudare questo metodo. Per esempio, diciamo di voler controllare che:

  • l'inverso di una lista vuota è una lista vuota
  • l'inverso della lista [1] è la lista [1]
  • l'inverso della lista [1,2,3] è la lista [3,2,1]

Creiamo un altro file che chiamiamo test_prova.py. Il nome esatto del file non importa, quello che importa è che inizi con "test_". Dentro il file, scriviamo die funzioni test_reverse_1 e test_reverse_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 reverse definita nel file prova.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)-1, -1, -1) mettiamo range(len(l)-1, 0, -1). Se rieseguiamo i test, otteniamo:


Si noti che test_reverse_1 ha avuto comunque successo, ma test_reverse_2 no! Di conseguenza la riga test_prova.py (che raggruppa tutti i test presenti nel file test_prova.py) e la riga tests (che raggruppa tutti i file di test) vengono segnalati anch'esse come errate.

Inoltre, sul codice sorgente di test_reverse_2 si hanno più dettagli sul motivo per cui il test è fallito:


Da questa pagina si evince che il problema è sulla riga "assert reverse([1]) == [1]". Poiché reverse adesso è bacata, l'esecuzione di reverse([1]) ha dato la lista vuota  come risultato, per cui la riga è diventata "assert [] == 1" che è ovviamente errata perché la lista a destra contiene più elementi di quella a sinistra.

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

Esercizi

In questi esercizi faremo esperimenti sulle liste e, nel contempo, sul collaudo delle funzioni.

Esercizio 0

Provare a replicare quanto descritto nella lezione qui sopra.

Esercizio 1

Scrivere un metodo cerca_positivo(l) che restituisce il primo elemento positivo della lista l, -1 se tale elemento non esiste. Scrivere un test che controlli la correttezza della funzione che avete scritto, provando quantomeno che
  • cerca_positivo([-4, 2, 0]) restisuice 2
  • cerca_positivo([-4, -2]) restituisce -1

Esercizio 2

Scrivere il metodo cerca_positivo_pos(l) che restituisce la posizione del primo elemento positivo presente nella lista l, -1 se tale elemento non esiste. Scrivere un test che controlli la correttezza della funzione che avete scritto, provando quantomeno che

  • cerca_positivo_pos([-4, 2, 0]) restisuice 1
  • cerca_positivo_pose([-4, -2]) restituisce -1

Esercizio 3

Scrivere il metodo count che prende come argomenti una lista l ed un oggetto x, e restituisce il numero di volte che x compare in l. Ad esempio, count([ 1, 2, 3, 1, 4], 1) restituisce 2 perché ci sono due elementi uguali ad un nella lista. Scrivere un test per verificarne il corretto funzionamento (inventatevi voi due o tre casi da controllare).

Esercizio 4

Si scriva una funzione che riceve in input (come argomenti) due liste di interi (si può assumere che siano della stessa lunghezza) e restituisce una nuova lista nel quale ogni elemento è ottenuto dalla somma dei corrispondenti elementi di input. Ad esempio, se in input riceve le liste [1, 2, 3] e [4, 5, 6], restituisce [5, 7, 9]. Scrivere un test per verificarne il corretto funzionamento.

Esercizio 5

Scrivere una funzione input_list_int() che restituisce una lista di numeri interi da tastiera. La funzione chiede ripetutamente all'utente di inserire un numero intero e si ferma quando l'utente preme Invio senza inserire alcun dato. A quel punto restituisce una lista con tutti i numeri inseriti.

Collaudare questa funzione è complicato perché, a differenza delle altre qui sopra, prende l'input da tastiera e non dagli argomenti, quindi soprassediamo.

Esercizio 6

Scrivere una funzione random_list(n, k) che restituisce una lista di n numeri interi, ognuno dei quali è un numero casuale tra 1 e k. Scrivere un test per verificarne il corretto funzionamento.




Last modified: Sunday, 17 December 2023, 6:38 PM