Gli enum sono semplicemente dei tipi finiti, con nomi personalizzati (si spera significativi). Un enum potrebbe avere un solo valore, come quello void
che contiene solo null
(alcune lingue lo chiamano unit
e usano il nome void
per un enum senza elementi!). Può avere due valori, come il bool
quale ha false
e true
. Potrebbe averne tre, come colourChannel
con red
, green
e blue
. E così via.
Se due enum hanno lo stesso numero di valori, allora sono "isomorfi"; cioè se cambiamo sistematicamente tutti i nomi allora possiamo usarne uno al posto di un altro e il nostro programma non si comporterà in modo diverso. In particolare, i nostri test non si comporteranno diversamente!
Ad esempio, result
contenente win
/lose
/ draw
è isomorfo a quanto sopra colourChannel
, poiché possiamo sostituire ad esempio 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. Qualsiasi " colourChannel
test" che abbiamo scritto passerà comunque, anche se non ce n'è colourChannel
più!
Inoltre, se un enum contiene più di un valore, possiamo sempre riorganizzarli per ottenere un nuovo enum con lo stesso numero di valori. Dal momento che il numero di valori non è cambiato, la nuova disposizione è isomorfa a quella precedente, e quindi potremmo cambiare tutti i nomi e i nostri test continuerebbero a passare (nota che non possiamo semplicemente cambiare la definizione; dobbiamo continua a disattivare anche tutti i siti di utilizzo).
Ciò significa che, per quanto riguarda la macchina, gli enum sono "nomi distinguibili" e nient'altro . L'unica cosa che possiamo fare con un enum è diramare se due valori sono uguali (eg red
/ red
) o diversi (eg red
/ blue
). Questa è l'unica cosa che può fare un "test unitario", 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, un 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'implementazione del linguaggio, dovresti testarlo dall'esterno , poiché non ci si può fidare di eseguire i test correttamente!
Quindi questa è la teoria; che dire della pratica? Il problema principale con questa caratterizzazione degli enum è che i programmi del "mondo reale" sono raramente autonomi: abbiamo versioni legacy, distribuzioni remote / incorporate, dati storici, backup, database live, ecc., Quindi non possiamo mai "cambiare" tutte le occorrenze di un nome senza perdere alcuni usi.
Eppure 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'enum è un'aringa rossa: cosa succede se un sistema ne ha bisogno questo modo, e un altro ha bisogno di essere quel modo? Non può essere entrambi, indipendentemente da quanti test scriviamo! Il vero colpevole qui è l'interfaccia di input / output, che dovrebbe produrre / consumare formati ben definiti piuttosto che "qualunque sia il numero intero interpretato dall'interprete". Quindi la vera soluzione è testare le interfacce di 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'altra parte.
Potremmo ancora chiederci se l'enum sia "esercitato abbastanza a fondo", ma in questo caso l'enum è di nuovo un'aringa rossa. Ciò di cui siamo effettivamente preoccupati è la suite di test stessa . Possiamo ottenere fiducia qui in un paio di modi:
- La copertura del codice può dirci se la varietà di valori enum provenienti dalla suite di test sono sufficienti per innescare 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.
- Il controllo delle proprietà può dirci se la varietà di rami nel codice è sufficiente per gestire le possibilità di runtime. Ad esempio, se il codice gestisce solo
red
e testiamo solo con red
, allora abbiamo una copertura del 100%. Un verificatore di proprietà (tenterà di) generare controesempi alle nostre asserzioni, come la generazione digreen
blue
valori e che abbiamo dimenticato di testare.
- I test di mutazione possono dirci se le nostre affermazioni effettivamente controllano l'enum, piuttosto che semplicemente seguire i rami e ignorare le loro differenze.