Tarin Gamberini

A software engineer and a passionate java programmer

Test di unità o di accettazione?

Come sapere se un comportamento del software debba essere verificato tramite un test di unità o di accettazione? Una storia, che vede come miglior attore non protagonista un'intera razza di capre, ci aiuta a capire alcuni sottili differenze.

Indice

Una storia di software e capre

Questo storia è puramente immaginaria, ogni eventuale riferimento a persone, cose o fatti reali è del tutto casuale.

Presso l'ente di formazione InsegnaMela nasce l'esigenza di informatizzare il registro delle presenze in aula. InsegnaMela incarica la CetrioloSoft per la progettazione del nuovo sistema informatico. Dopo alcuni incontri preliminari InsegnaMela e CetrioloSoft convengono di adottare BDD per la progettazione di un sistema costituito da:

  • CEGIGI: una app installata sui dispositivi mobili dei docenti per fare l'appello delle lezioni
  • REGISTRO: un web service REST che, fra i vari servizi, espone anche quello che riceve gli appelli inviati dai docenti tramite CEGIGI
  • una applicazione web a disposizione della segreteria dell'ente di formazione che accede alla banca dati del REGISTRO

Durante una delle riunioni dei tre amigos:

... queste riunioni sono dette «riunioni dei tre amigos» perché coinvolgono tre differenti competenze del team: test, sviluppo, prodotto.

i tre amigos scrivono la seguente funzionalità:

scarico-persone-in-aula.feature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# language: it
Funzionalità: Scarico persone in aula
  Un docente per fare l'appello ad una lezione di un corso ha
  bisogno di sapere quali sono le persone in aula.

  Scenario: Scarico persone in aula
    Dato che il docente usa il cellulare di numero "123 45 67 890"
    E che il docente sta per fare una lezione del corso "Allevamento della capra trentina" (idCorso=1234)
    Quando il docente scarica le persone in aula  ("GET", "/corso/1234/persone_in_aula.json")
    Allora l'app "CEGIGI" ottiene in risposta ("JSON") dal web service "REGISTRO" le persone:
    """
    {
      "personeInAula": [
        {"idPartecipante":"1","nome":"QUI"},
        {"idPartecipante":"2","nome":"QUO"},
        {"idPartecipante":"3","nome":"QUA"}
      ]
    }
    """

I lavori procedono senza imprevisti finché, un giorno, l'app CEGIGI va in errore. Anna, che sta sviluppando l'app, si accorge che quando si scaricano le persone in aula per una serie di corsi non ci sono problemi, ma quando CEGIGI prova a scaricare le persone in aula del corso Allevamento della capra trentina il servizio web REGISTRO risponde 500: internal server error. Così Anna riferisce il problema a Saverio, che sta sviluppando il REGISTRO.

Saverio si accorge che per il corso Allevamento della capra trentina non ci sono persone in aula: un caso a prima vista senza senso. Saverio, che adotta TDD, scrive un test di unità per riprodurre l'errore. Esegue il test ed ottiene l'errore 500: internal server error riferitogli da Anna, molto bene. Ora Saverio scrive la minima quantità di codice sorgente necessaria per evitare il verificarsi dell'errore. Nel caso particolare in cui non ci siano persone in aula REGISTRO restituisce a CEGIGI:

  • un elenco vuoto;
  • il messaggio di errore per il docente “Non ci sono persone in aula per questo corso”.

Infine Anna verifica che in questo caso l'app CEGIGI:

  • visualizzi al docente il messaggio di errore “Non ci sono persone in aula per questo corso”;
  • non visualizzi alcun bottone Invia appello;
  • visualizzi il bottone Termina lezione.

Alla successiva riunione dei tre amigos, durante una pausa caffè, Saverio chiede ad Amelia, la responsabile formazione di InsegnaMela, chi fosse mai interessato alla capra trentina. Dopo un sorso di caffè caldo Saverio nota che Amelia lo sta fissando con gli occhi sgranati e la bocca spalancata 😨.

La riunione riprende ed Amelia, con un sorriso preoccupato, confessa di essersi dimenticata di GentilMente, una consociata di InsegnaMela che eroga corsi di yoga e meditazione. Tramite GentilMente sono entrati in contatto con allevatori tibetani che per diversificare i loro prodotti hanno importato la capra lanuginosa del trentino. Per farla breve alcuni corsi di InsegnaMela sono in streaming con un ufficio postale tibetano che li registra e poi li consegna su cassette VHS agli allevatori. Quei corsi hanno zero persone in aula, ed è corretto che sia così. Infatti in quei casi ad InnovaMela interessa rendicontare solo il lavoro del docente e non la frequenza dei partecipanti al corso.

La costruzione del sistema informatico è però già in stadio avanzato per cui si conviene di non gestire in un modo specifico questa tipologia di corsi intervenendo sul codice sorgente; si sceglie invece di gestire questa tipologia come una caso “degenere” di corso e lo si documenta in un apposito scenario:

scarico-corso-per-gentilmente.feature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# language: it
Funzionalità: Scarico corso per GentilMente
  Un docente non deve fare l'appello ad una lezione di un corso
  che non ha persone in aula.

  Scenario: Scarico corso per GentilMente
    Dato che il docente usa il cellulare di numero "123 45 67 890"
    E che il docente sta per fare una lezione ad un corso con 0 persone in aula (idCorso=1234)
    Quando il docente scarica le persone in aula  ("GET", "/corso/1234/persone_in_aula.json")
    Allora l'app "CEGIGI" ottiene in risposta ("JSON") dal web service "REGISTRO" le persone:
    """
    {
      "personeInAula": [],
      {"message":"Non ci sono persone in aula per questo corso"}
    }
    """

Trasformare un test di unità in un test di accettazione

Fortunatamente Saverio aveva già scritto un test di unità per il caso “degenere”. Infatti, in tal caso, il servizio web REGISTRO funziona già correttamente. Si tratta solo di trasformare tale test di unità in un test di accettazione. In particolare i dati di ingresso al test di unità andranno divisi in due parti: la prima andrà a costituire l'implementazione del primo step della feature scarico-corso-per-gentilmente.feature:

1
2
3
4
@Dato("^che il docente usa il cellulare di numero \"(.*?)\"$")
public void cheIlDocenteUsaIlCellulareDiNumero(String numCel) {
    ...
}

mentre la seconda andrà nell'implementazione del secondo step:

1
2
3
4
@E("^che il docente sta per fare una lezione ad un corso con (\d+) persone in aula \(idCorso=(\d+)\)$")
public void cheIlDocenteStaPerFareUnaLezioneAdUnCorsoConPersoneInAulaIdCorso(int zero, int idCorso) {
    ....
}

L'invocazione del servizio REST andrà spostata nell'implementazione dello step:

1
2
3
4
@Quando("^il docente scarica le persone in aula  \(\"(.*?)\", \"(.*?)\"\)$")
public void ilDocenteScaricaLePersoneInAula(String method, String restResourceDotJson) {
    ...
}

Infine le varie Assert del test di unità andranno a costituire l'implementazione dell'ultimo step definition:

1
2
3
4
@Allora("^l'app \"(.*?)\" ottiene in risposta \(\"(.*?)\"\) dal web service \"(.*?)\" le persone:$")
public void lAppOttieneInRispostaDalWebServiceLePersone(String appName, String responsContentType, String restWsName, String responseContentJson) {
    ...
}

A volte, invece, si è nella situazione opposta, ossia si è nel caso di dover trasformare un test di accettazione in uno (o più) test di unità. Basti pensare, infatti, a quali dettagli siano presenti in uno scenario che ostacolano l'emergere del linguaggio condiviso. Tali dettagli appesantiscono la lettura dello scenario senza comunicare utili informazioni al business. Chiediamoci, per esempio, quanto le seguenti informazioni presenti nella feature scarico-persone-in-aula.feature vista sopra siano, o meno, comunicative per le persone del business:

scarico-persone-in-aula.feature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# language: it
Funzionalità: Scarico persone in aula
  Un docente ...

  Scenario: ...
    Dato ...
    E ... (idCorso=1234)
    Quando ... ("GET", "/corso/1234/persone_in_aula.json")
    Allora ... ("JSON") dal web service ...:
    """
    {
      "personeInAula": [
        {"idPartecipante":"1","nome":"QUI"},
        {"idPartecipante":"2","nome":"QUO"},
        {"idPartecipante":"3","nome":"QUA"}
      ]
    }
    """

Dalla piramide all'iceberg

Ma allora come decidere se un comportamento del software debba essere scritto in un test di unità o di accettazione? Quanti test di unità si dovrebbero scrivere? Esiste un numero ragionevole di test di accettazione?

Nel libro Succeeding with Agile: Software Development Using Scrum Mike Cohn propone un modello a piramide caratterizzato da: molti unit test (la base della piramide), un numero minore di test di integrazione (il corpo della piramide), ed un ancor minore numero di test end-to-end (il vertice della piramide). Martin Fowler, nel suo post TestPyramid, riconosce che il modello a piramide abbia raggiunto una notevole popolarità nei circoli Agile, tuttavia ritiene che vi sia ancora molto da dire riguardo a come costruire un buon portfolio di test.

Seb Rose, Matt Wynne e Aslak Hellesoy nel loro libro The Cucumber for Java Book - Behaviour-Driven Development for Testers and Developers affermano che la decisione riguardo al fatto che un comportamento del software debba, o meno, essere specificato in un feature file dovrebbe dipendere solo dall'interesse che le persone del business hanno in tale comportamento. La storia di software e capre evidenzia proprio questo punto: finché Amelia (persona del business) si era dimenticata dell'esistenza di corsi con zero persone in aula, allora il corretto comportamento del REGISTRO era verificato da un test di unità; non appena Amelia dimostra interesse per il comportamento del software in questo frangente allora il test di unità si trasforma in test di accettazione.

I test di accettazione lavorano su funzionalità di interesse per il business e si implementano, solitamente, come test end-to-end (dall'interfaccia utente, al database, e ritorno). Tuttavia Seb, Matt e Aslak evidenziano una sottile peculiarità dei testi di accettazione: essi non sono solo al vertice della piramide del modello di Cohn. Infatti, nella storia di software e capre è proprio un test di unità ad essere trasformato in un test di accettazione, e quest'ultimo ha le stesse caratteristiche di un test di unità perché agisce sul servizio REST isolato. Per visualizzare tale concetto Seb, Matt e Aslak propongono un modello di test ad iceberg.

Nell'iceberg la piramide è inclinata in modo tale che ogni tipologia di test possa emergere sopra il livello del mare. I test emersi sono quelli che sono stati implementati come test di accettazione e, in quanto tali, sono leggibili dalle persone del business perché documentati in un feature file.

Invia un commento

Un commento è inviato attraverso una normalissima e-mail. Il tuo indirizzo e-mail non sarà pubblicato o diffuso.

Questo blog è moderato, per cui alcuni commenti potrebbero non essere pubblicati. I commenti sono solitamente approvati dal moderatore in uno/tre giorni.