Le enumerazioni sono semplicemente tipi finiti, con nomi personalizzati (si spera che siano significativi). Un enum potrebbe avere solo un valore, come void
che contiene solo null
(alcuni linguaggi chiamano questo unit
, e usa il nome void
per un enum con elementi no !). Può avere due valori, come bool
che ha false
e true
. Può avere tre, come colourChannel
con red
, green
e blue
. E così via.
Se due enumerazioni hanno lo stesso numero di valori, allora sono "isomorfe"; Ad esempio, se cambiamo sistematicamente tutti i nomi, possiamo usarne uno al posto di un altro e il nostro programma non si comporterà diversamente. In particolare, i nostri test non si comportano diversamente!
Ad esempio, result
contenente win
/ lose
/ draw
è isomorfo al colourChannel
sopra, dato che possiamo sostituire ad es. colourChannel
con result
, red
con win
, green
con lose
e blue
con draw
, e finché lo facciamo ovunque (produttori e consumatori, parser e serializzatori, voci di database, file di registro, ecc.) quindi non ci saranno cambiamenti nel nostro programma. Tutti i " colourChannel
test" che abbiamo scritto passeranno comunque, anche se non c'è più colourChannel
!
Inoltre, se un enum contiene più di un valore, possiamo sempre riorganizzare quei valori per ottenere un nuovo enum con lo stesso numero di valori. Dato che il numero di valori non è cambiato, la nuova disposizione è isomorfa a quella vecchia, e quindi potremmo cambiare tutti i nomi e i nostri test sarebbero comunque passati (si noti che non possiamo solo cambiare la definizione, dobbiamo ancora cambiare tutti i siti di utilizzo).
Ciò significa che, per quanto riguarda la macchina, le enumerazioni sono "nomi distinguibili" e nient'altro . L'unica cosa che possiamo fare con un enum è ramificare se due valori sono uguali (ad esempio red
/ red
) o diversi (ad esempio red
/ blue
). Questa è l'unica cosa che un "test unitario" può fare, ad es.
( red == red ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
( red != green) || throw TestFailure;
( red != blue ) || throw TestFailure;
...
Come dice @ jesm00, tale test sta controllando l'implementazione della lingua piuttosto che il tuo programma. Questi test non sono mai una buona idea: anche se non ti fidi dell'attuazione del linguaggio, dovresti testarlo dall'esterno , dato che non può essere considerato attendibile per eseguire correttamente i test!
Quindi questa è la teoria; che dire della pratica? Il problema principale di questa caratterizzazione delle enumerazioni è che i programmi "mondo reale" sono raramente autosufficienti: abbiamo versioni legacy, implementazioni remote / embedded, dati storici, backup, database live ecc. Quindi non possiamo mai veramente "cambiare" tutte le occorrenze di un nome senza perdere alcuni usi.
Tuttavia, queste cose non sono la "responsabilità" dell'enum stesso: cambiare un enum potrebbe interrompere la comunicazione con un sistema remoto, ma al contrario potremmo risolvere un tale problema cambiando un enum!
In tali scenari, l'enumerazione è un'arma rossa: cosa succede se un sistema ha bisogno che sia questo , e un altro ha bisogno che sia tale modo? Non può essere entrambi, non importa quanti test scriviamo! Il vero colpevole è l'interfaccia di input / output, che dovrebbe produrre / consumare formati ben definiti piuttosto che "qualunque sia il valore dell'interpretazione". Quindi la vera soluzione è testare le interfacce i / o : con unit test per verificare che stia analizzando / stampando il formato previsto e con test di integrazione per verificare che il formato sia effettivamente accettato dall'altro lato .
Potremmo ancora chiederci se l'enumerazione sia stata "esercitata a sufficienza", ma in questo caso l'enumerazione è di nuovo una falsa pista. Ciò di cui siamo veramente preoccupati è la suite di test stessa . Possiamo acquisire sicurezza qui in due modi:
- La copertura del codice può dirci se la varietà dei valori di enum provenienti dalla suite di test è sufficiente per attivare i vari rami nel codice. In caso contrario, possiamo aggiungere test che attivano i rami scoperti o generare una più ampia varietà di enumerazioni nei test esistenti.
- Controllo proprietà può dirci se la varietà di rami nel codice è sufficiente per gestire le possibilità di esecuzione. Ad esempio, se il codice gestisce solo
red
e testiamo solo con red
, abbiamo una copertura del 100%. Un controllore di proprietà (provate a) genererà controesempi alle nostre asserzioni, come generare i valori di green
e blue
che abbiamo dimenticato di testare.
- I test di mutazione possono dirci se le nostre asserzioni in realtà controllano l'enumerazione, piuttosto che seguire semplicemente i rami e ignorarne le differenze.