Esistono vari modi in Python per eseguire test e sono applicabili non solo al caso delle interfacce web ma a qualsiasi altra attività applicativa. Qui ne vedremo due ma opportune ricerche su documentazione e motori di ricerca dimostreranno che esistono molte altre alternative.
Li sperimenteremo trasformando in test le operazioni che abbiamo svolto con Selenium su un tastierino numerico web di cui riportiamo qui di seguito per comodità il codice HTML e JavaScript:
<!DOCTYPE html>
<html>
<head>
<title>Tastierino Numerico</title>
<style>
table {
width: auto;
margin: auto;
}
td {
text-align: center;
}
.da0a9{
height:70px;
width:70px;
}
.rigaintera{
height: 50px;
width: 200px;
text-align: right;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<table>
<tr>
<td colspan="3"><input class="rigaintera" id="display" readonly></td>
</tr>
<tr>
<td><button class="da0a9">1</button></td>
<td><button class="da0a9">2</button></td>
<td><button class="da0a9">3</button></td>
</tr>
<tr>
<td><button class="da0a9">4</button></td>
<td><button class="da0a9">5</button></td>
<td><button class="da0a9">6</button></td>
</tr>
<tr>
<td><button class="da0a9">7</button></td>
<td><button class="da0a9">8</button></td>
<td><button class="da0a9">9</button></td>
</tr>
<tr>
<td colspan="3"><button class="da0a9">0</button></td>
</tr>
</table>
<script>
$(document).ready(function () {
$('button').click(function () {
$( "#display" ).val( $( "#display" ).val() +$(this).text() );
});
});
</script>
</body>
</html>
Per entrambe le modalità che vedremo svolgeremo due test:
- uno verificherà che il valore restituito dal display vuoto sia una stringa;
- l'altro consisterà nella verifica della corretta digitazione automatica di una sequenza decisa a priori (la stessa operazione svolta nell'esempio della lezione in cui abbiamo presentato il tastierino).
Usare gli assert di Python
La prima modalità sfrutta la parola chiave assert
. Questa è molto nota ed utilizzata dai programmatori Python e riceve come argomento un'operazione da svolgere che restituisca un valore booleano. Se questa avrà come risultato true
non succederà niente, altrimenti verrà lanciato un errore di tipo AssertionError
eventualmente gestibile con un costrutto try...except
. Si potrà anche passare un ulteriore parametro che indicherà, in caso di errore, il messaggio da riportare.
Se tutti gli assert
che abbiamo predisposto nei test non avranno causato errori, la nostra applicazione avrà superato l'intera batteria di prove.
Per creare un test con gli assert
non faremo altro che creare una sequenza di funzioni in ognuna delle quali avvieremo una sessione driver completa e alla fine del codice metteremo un assert
in cui dovremo cercare di svolgere un test adatto a confermare la nostra ipotesi di esecuzione.
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_tipo_display():
driver = webdriver.Chrome()
driver.get('http://localhost/tastierino.html')
# verifica del tipo di valore del campo diplay
assert isinstance(driver.find_element(By.XPATH,f'//input[@id="display"]').get_attribute('value'), str)
print("Test TIPO DISPLAY eseguito: OK")
driver.quit()
def test_tastierino():
driver = webdriver.Chrome()
driver.get('http://localhost/tastierino.html')
#sequenza per la sperimentazione
sequenza='78935627'
# leggiamo la sequenza un carattere alla volta
for carattere in sequenza:
# con XPath individuiamo il pulsante e lo clicchiamo
driver.find_element(By.XPATH,f'//button[normalize-space()="{carattere}"]').click()
# verifichiamo che la sequenza che appare a display sia quella desiderata
assert driver.find_element(By.XPATH,f'//input[@id="display"]').get_attribute('value')==sequenza
print("Test DIGITAZIONE SEQUENZA eseguito: OK")
driver.quit()
test_tipo_display()
test_tastierino()
Le funzioni
Come si vede abbiamo creato due funzioni in ognuna delle quali l'ipotesi da verificare è stata trasformata in un confronto in modo da poterlo valutare con un assert
. Rispetto agli esempi delle lezioni precedenti, notiamo che in ogni funzione viene messo al lavoro Selenium per azionare l'interfaccia ma si finisce sempre a svolgere un confronto che possa essere valutato con un assert.
Nel primo caso, si verifica che il display (individuato con //input[@id="display"]
) sia un'istanza, isinstance
, della classe str
.
Nel secondo recuperiamo il valore del display e controlliamo che sia uguale alla stringa sequenza
. Si ricordi che se uno dei test fallisce, l'errore di tipo AssertionError
prodotto fermerà il programma e non farà eseguire gli altri test. Pertanto, con questo approccio, si inseriscano dove si preferisce dei controlli try...except
per gestire opportunamente esecuzione e reportistica delle prove.
Creazione di unit test
Come seconda modalità, vedremo la libreria unittest
, già presente nell'installazione del linguaggio. Come indica il suo nome, è pensata per test unitari sebbene la si possa sfruttare per test più complessi, di integrazione, cosa che faremo nelle prossime lezioni. Tuttavia, si tenga sempre presente che il mondo Python è pienissimo di alternative interessanti a questi strumenti.
Come mostra il codice che segue, unittest
può essere vista come una sorta di strutturazione più rigida del meccanismo degli assert
infatti dovremo:
- creare una classe derivante da
unittest.TestCase
. Questa rappresenterà il nostro test case ovvero l'insieme di test da avviare; - predisporre una funzione per ogni test che nel nostro caso saranno due. Si noti che ogni test termina con la chiamata ad un metodo che deriva proprio da
assert
. Qui useremoassertEqual
per verificare l'uguaglianza eassertIsInstance
per dimostrare l'appartenenza ad una specifica classe di un valore. Esiste una lista completa dei metodiassert*
esistenti nella documentazione ufficiale; - preparare un metodo
setUp
per l'inizializzazione del test il quale verrà eseguito prima di ogni test; - definire un metodo
tearDown
per la finalizzazione del test che sarà eseguito alla fine di ogni test; - avviare il testcase mediante invocazione del metodo
main
.
Il test con Python
Questo il nostro test:
from selenium import webdriver
from selenium.webdriver.common.by import By
import unittest
class Testing(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.get('http://localhost/tastierino.html')
def test_display(self):
self.assertIsInstance(self.driver.find_element(By.XPATH,f'//input[@id="display"]').get_attribute('value'), str)
def test_tastierino(self):
# sequenza per la sperimentazione
sequenza='78935627'
# leggiamo la sequenza un carattere alla volta
for carattere in sequenza:
# con XPath individuiamo il pulsante e lo clicchiamo
self.driver.find_element(By.XPATH,f'//button[normalize-space()="{carattere}"]').click()
# verifichiamo che la sequenza che appare a display sia quella desiderata
self.assertEqual(self.driver.find_element(By.XPATH,f'//input[@id="display"]').get_attribute('value'),sequenza)
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main()
Come vedremo sperimentandolo, in questo approccio tutti i test saranno comunque eseguiti, anche in caso di fallimento, e alla fine - oltre ad una dettagliata descrizione di errore - ci verrà fornito un responso sintetico come:
Ran 2 tests in 19.010s
OK
in caso di successo di tutti i test, oppure:
Ran 2 tests in 14.788s
FAILED (failures=1)
che ci notificherà il fallimento totale o parziale del test case e ci indicherà quanti sono i test falliti (abbiamo provocato volutamente il fallimento del secondo test provando una sequenza sbagliata).
I metodi setUp
e tearDown
sono stati usati, rispettivamente, per avviare il driver e chiuderlo in modo da avere punti unitari in cui concentrare tali operazioni comuni a tutti i test.
Soprattutto in fase di apprendimento di questi strumenti non si esiti a fare uso di istruzioni print
per stampare messaggi in modo da arricchire la reportistica prodotta e seguire bene il procedere dell'esecuzione.