I record di identità nelle fixture di test sono le righe che una suite di test conserva in modo permanente: la persona che ha l’età richiesta, quella che non ce l’ha, il cliente il cui indirizzo è un’unica stringa ininterrotta, l’account senza cognome. Sono economici da creare e facili da mantenere male, e un insieme di fixture che si disallinea dal prodotto è peggio di nessuna fixture, perché infonde falsa fiducia.
Questa guida tratta come organizzare i record di identità all’interno di un insieme di fixture, quali casi limite vale la pena conservare e come impedire che la raccolta marcisca in silenzio.
Cosa appartiene a una fixture anziché a un generatore?
Una fixture si usa quando il test asserisce su un esito specifico, e un generatore si usa quando il test va a caccia di problemi di input sconosciuti. La distinzione non riguarda la dimensione o la formalità; riguarda se qualcuno debba conoscere l’input in anticipo.
Ciò significa che le fixture servono al comportamento: data questa persona, il sistema deve prendere questo ramo. I generatori servono all’esplorazione: dai al sistema molte persone plausibili e vedi cosa si rompe. Un test che asserisce su una risposta ma estrae il suo input in modo casuale non sta testando il comportamento che dichiara di testare, perché l’esecuzione successiva può esercitare un ramo diverso e smettere di coprire il caso che l’autore aveva in mente.
La maggior parte delle suite ha bisogno di entrambi, e la convenzione utile è rendere visibile la distinzione. Le fixture vivono in file con valori stabili e nomi leggibili. I record generati vivono in un passo che viene eseguito al momento del test. Mescolare i due in un unico file è il modo in cui una suite diventa impossibile da ragionare sei mesi dopo.
Perché “casuale ogni volta” rende i guasti irriproducibili?
Perché il report del guasto non contiene alcun input. Un test che estrae un record fresco a ogni esecuzione produce una traccia dello stack, uno screenshot e nulla più; l’esecuzione successiva può passare, e l’ingegnere resta a indovinare se la correzione abbia funzionato o se siano cambiati i dadi.
Questa è la singola fonte più comune di test instabili nei sistemi ad alta intensità di dati, e il danno si accumula. Gli ingegneri imparano a rieseguire i test falliti finché non diventano verdi, il che lentamente abitua l’intero team a ignorare il segnale che la suite esiste per fornire. La riproducibilità qui non è un lusso; è la proprietà che rende significativo il resto della suite.
La correzione è fissare l’input e lasciare che la variazione provenga da un luogo controllato. Dove un record viene generato anziché scritto a mano, derivarlo da una chiave fissa dà la stessa persona a ogni esecuzione, così un’asserzione resta stabile e un report di bug può nominare il record esatto che ha visto.
Come dovrebbero essere nominati e raggruppati i record delle fixture?
Nomina ogni record in base alla situazione che esiste per testare, non in base ai valori che contiene. Un record chiamato per esempio richiedente-maggiore-di-diciotto dice al lettore successivo a cosa serve; un record chiamato in base a un cognome non gli dice nulla e verrà riutilizzato per lo scopo sbagliato entro un mese.
Il raggruppamento segue la stessa logica. Un file di fixture per funzionalità o scenario, contenente solo le persone di cui quello scenario ha bisogno, mantiene il file leggibile e rende ovvio quando un record è diventato inutilizzato. Un unico file enorme da cento record è lo schema che produce suite in cui nessuno sa dire quale fixture conti ancora, così nessuno osa cancellarne alcuna.
Due pratiche più piccole si ripagano da sole. Metti un commento in cima a ogni gruppo di fixture che dichiari a cosa serve il gruppo e quando è stato rivisto l’ultima volta. E tieni i valori nella fixture, non nel codice del test, così che cambiare un record non richieda di modificare le asserzioni.
Quali casi limite di identità meritano una fixture permanente?
Una lista breve copre una quantità sorprendente di rischio, perché ogni voce rompe un’assunzione diversa anziché un valore diverso.
| Fixture | L’assunzione che rompe |
|---|---|
| Una persona di oltre un secolo | Che gli anni di nascita siano sempre entro un intervallo ristretto |
| Un nome molto più lungo di quanto l’interfaccia consenta | Che la larghezza di un campo campione sia rappresentativa |
| Una persona senza cognome | Che esistano sempre due parti del nome |
| Un nome in una scrittura non latina o con diacritici | Che l’alfabeto sia sempre quello latino |
| Un record con un identificatore opzionale assente | Che ogni campo sia sempre popolato |
| Una data di nascita in un giorno bisestile | Che ogni data ricorra ogni anno |
| Un record con una coppia deliberatamente incoerente | Che le regole di coerenza siano ancora applicate |
L’ultima voce è quella che i team più spesso omettono, ed è la più preziosa. È l’unica fixture che fallisce quando una regola di coerenza viene disattivata per errore, e le regole di coerenza sono esattamente il tipo di codice che viene allentato durante un incidente e mai più stretto.
Come marciscono le fixture, e cosa lo impedisce?
Una fixture non diventa sbagliata da sola; il sistema che la circonda cambia. Una soglia si sposta da un’età a un’altra e la persona che era appena abbastanza grande non lo è più. Una regola di validazione si stringe e un record che un tempo passava ora fallisce, il che trasforma un test che passava in rosso per una ragione non correlata al codice sotto test. Arriva un cambiamento di formato globale e metà del file di fixture diventa obsoleto senza che nessuno se ne accorga.
Tre abitudini rallentano tutto questo. Le fixture dipendenti da una data dovrebbero dichiarare la data che presuppongono, così che il passare del tempo sia visibile nel file anziché scoperto in una build rossa. Le fixture dovrebbero essere esercitate regolarmente anziché solo quando la loro funzionalità cambia, così che la rottura emerga presto invece che durante una modifica non correlata. E le revisioni dovrebbero chiedere se ogni record meriti ancora il suo posto, perché il costo di una fixture non è la sua creazione ma la confusione che causa una volta dimenticato il suo scopo.
Il punto più profondo è che le fixture sono dati con un contratto di manutenzione, e una suite che le tratta come storia immutabile finirà per testare il prodotto com’era, non com’è.
Le fixture vanno generate?
In parte, e vale la pena essere deliberati sulla suddivisione. I record che esistono per fissare il comportamento dovrebbero essere scritti per esteso, perché qualcuno deve poterli ispezionare e ragionarci sopra. I record che esistono per fornire volume — cento righe per un test di importazione, mille per una prova di migrazione — è meglio derivarli, perché nessuno può leggere cento righe e i loro valori esatti non contano purché conti la forma.
La ragione per derivare il volume anziché versionarlo è la riproducibilità su larga scala. Un file di mille righe versionato è un onere di manutenzione che cresce a ogni modifica dello schema, mentre un lotto derivato può essere ricostruito nel momento in cui lo schema si sposta, e ricostruito in modo identico se è guidato da una chiave fissa. I record estratti dal generatore di identità e dati di test funzionano in questo ruolo: coerenti al proprio interno per paese, riproducibili da una chiave e chiaramente sintetici, così che nessuno scambi una riga per un cliente reale.
Per il piccolo insieme scritto a mano vale l’opposto. Mantienilo minuscolo, mantienilo leggibile e mantieni ogni record legato a uno scenario nominato, così che cancellarne uno sia una decisione informata anziché un’ipotesi. Tutto, in entrambe le metà dell’insieme, è inventato per i test software; nulla di esso descrive una persona reale e nulla di esso può essere usato come identità di qualcuno.
Per gli sviluppatori: strutturare l’insieme di fixture
Tratta i dati delle fixture come parte del codice di test e applica gli stessi standard. Ogni record ha bisogno di un nome che dichiari il suo scenario, di un breve commento che spieghi perché esiste e di un posto in un gruppo abbastanza piccolo da leggere in una sola seduta. I valori vivono in file di dati anziché nelle asserzioni, così che un record possa essere aggiornato in un unico posto.
Poi fai in modo che la suite dimostri i propri input. Esegui il controllo di coerenza contro le fixture stesse, non solo contro i dati in arrivo, così che una fixture che si contraddice fallisca rumorosamente invece di testare in silenzio il percorso tollerante. Inietta la data corrente anziché leggerla, così che una fixture con una data al confine non cambi significato da un giorno all’altro. E mantieni nell’insieme un unico record deliberatamente rotto, di cui si asserisce il rifiuto, come prova permanente che le protezioni sono ancora attive.
Infine, registra la provenienza. Una nota di una riga che dice che questi record sono sintetici e generati per i test protegge il lettore successivo dall’assumere che provengano da qualche database, che è esattamente l’assunzione che porta un giorno ad aggiungere al file un record reale perché era comodo.
Passi successivi
Apri il tuo file di fixture più grande e prova a cancellare i dieci record dai nomi più vaghi; qualunque cosa tu non riesca a giustificare è un record che nessuno capisce. Poi aggiungi il record deliberatamente incoerente se manca, asserisci che il sistema lo rifiuti e conferma che la suite diventa rossa quando quel controllo viene disabilitato. La guida alla coerenza dei campi spiega gli abbinamenti che quel record dovrebbe rompere, e un lotto fresco dal generatore di identità copre la metà del volume dell’insieme.