Alcune settimane fa qualcuno ha scoperto che la sua banca non gli permetteva di fare un bonifico bancario di un valore molto specifico: R$17,99 (circa $3,50 USD). Cosa succedeva? La banca convertiva automaticamente il valore trasferito in R$17,98.
La cosa più curiosa è che i trasferimenti di altri valori funzionavano perfettamente. Beh, quasi tutti. Le persone hanno iniziato a scoprire altri casi specifici dove il problema si verificava: R$32,23 o R$155,17.
L’idea di questo post non è discutere la causa del problema, ma mostrare una tecnica che può aiutare i team a rilevare problemi simili.
Il testo include del codice in Elixir, ma include anche illustrazioni per spiegare ogni concetto. L’obiettivo è rendere questo contenuto utile sia per le persone tecniche che non tecniche.
I test unitari aiuterebbero? Beh… non esattamente.
Non fraintendermi: sono un fan dei test unitari e (quasi) sempre pratico TDD.
Ma un test convenzionale per una funzione di trasferimento di denaro sarebbe probabilmente qualcosa di simile all’esempio qui sotto:
Non è troppo specifico?
Nota che il test dove il trasferimento è possibile si concentra su un solo caso: un trasferimento di R$25,00. E in questo caso, i test hanno successo:

Ma possiamo scrivere un test per il caso dei R$17,99, giusto?
Certo! Dopo aver saputo dell’esistenza dell’errore potremmo includere un test aggiuntivo per garantire che anche i bonifici bancari di R$17,99 possano essere effettuati:
Qui possiamo mettere in azione il famoso ciclo TDD (Test Driven Development): Red, Green, Refactor.

Questo test fallisce (RED), e poi possiamo correggere il problema finché il test non passa (GREEN). E ora, avendo il test come rete di sicurezza possiamo alterare il codice per renderlo più leggibile senza paura di rompere nulla (REFACTOR).
Il test fallisce perché il saldo rimanente era R$2,02 e non R$2,01
Fatto! Ora quando faremo passare questo test garantiremo che i trasferimenti di R$17,99 funzioneranno perfettamente. Ma alcune domande rimangono:
-
Il trasferimento di R$32,23 funzionerà?
-
E se ci sono altri valori che generano lo stesso errore?
-
E se ci sono valori che generano un errore diverso?
-
Come possiamo impedire che problemi come questo raggiungano i nostri clienti?
Ed è qui che entra in gioco il PBT — Property Based Testing, ovvero Test Basati su Proprietà.
PBT: pulire dove i test convenzionali non arrivano
È piuttosto semplice scrivere il test dei R$17,99 dopo che sappiamo dell’esistenza dell’errore.

Quello che succede è che scriviamo test con casi che già abbiamo in mente, in modo molto lineare e specifico. Ma il test è così specifico che testa solo un caso. E se il problema continua a verificarsi con trasferimenti di altri valori?
In questo caso, il problema è apparso solo quando si è provato a trasferire R$6,05
Ma… come potremmo identificare valori problematici prima ancora di sapere del problema? È qui che entrano in gioco i test basati su proprietà.
Lavorare con le proprietà significa non pensare a casi specifici, ma piuttosto alle caratteristiche di ogni variabile coinvolta nel test. In questo caso, possiamo dire che:
-
Il saldo iniziale di chi sta per trasferire il denaro può essere qualsiasi valore maggiore di zero.
-
Il valore del trasferimento è maggiore di zero e minore o uguale al saldo disponibile.

Per sapere se il trasferimento è stato fatto con i valori corretti, basta “verificare il nostro lavoro”, come facevamo alle elementari. :)
Valore trasferito + Saldo rimanente = Saldo prima del trasferimento
Quando definiamo le regole in questo modo, gli strumenti di test automatizzati sono in grado di validare diversi casi differenti. E molte volte questi “casi diversi” finiscono per includere situazioni che non abbiamo mai nemmeno immaginato, e quindi non scriveremmo mai un test convenzionale su di esse.
Ora, eseguendo i test scopriamo che ci sono altri valori che generano anch’essi l’errore:
Ops! Se ho R$0,98 e provo a trasferire R$0,71 il problema si verifica anche!
Ah! Nota che la libreria di test dice: Counter example stored.
Le librerie PBT di solito memorizzano i valori che hanno generato errori e li usano di nuovo finché il problema non viene risolto. Cioè, quando il test passa è perché il problema è stato risolto, e non perché la libreria ha selezionato solo valori per i quali il problema non si verifica.
Ok, ma cosa c’entra questo con l’agilità?
Beh, l’agilità è la capacità di cambiare e generare risultati rapidamente.
È come essere in un’auto da corsa su una pista tortuosa: per fare curve velocemente e senza perdere molta velocità avrai bisogno di un veicolo solido e ben costruito. Senza di esso, sei limitato a due opzioni:
-
Fare la curva moooolto lentamente per assicurarti che il veicolo non si rompa
-
Fare la curva velocemente e smontare il tuo veicolo
Esempio:
Immagina che la banca decidesse di permettere conti in Bitcoin. Dovrebbero ora lavorare con una valuta che ha molti più di due decimali.
Ora immagina che la banca possa avere un’applicazione che:
a) Non ha test automatizzati
b) Ha alcuni test automatizzati
c) Ha test automatizzati convenzionali e alcuni che usano PBT
In quale scenario la banca sarebbe in grado di avere un prodotto funzionante in produzione più rapidamente?
In sintesi: Non c’è agilità senza eccellenza tecnica.
Codice Sorgente e considerazioni finali
Ho scritto il codice per generare l’errore di proposito e quindi illustrare come una certa tecnica potrebbe aiutare a risolvere un problema.
Se sei curioso, il codice usato in questo post è disponibile su: https://github.com/mariomelo/post_pbt