Si dovrebbero testare i valori di un enum usando unit test?


15

Se si dispone di un enum con solo valori (nessun metodo come si potrebbe fare in Java) e questo enum fa parte della definizione aziendale del sistema, si dovrebbe scrivere un test unitario per esso?

Stavo pensando che dovevano essere scritti, anche se potevano sembrare semplici e ridondanti, ritengo che ciò che riguarda le specifiche aziendali debba essere esplicitamente scritto in un test, sia che si tratti di unità / integrazione / ui / ecc. test o usando il sistema dei tipi di linguaggio come metodo di test. Dal momento che i valori che un enum (ad es. In Java) deve avere, dal punto di vista del business, non possono essere testati usando il sistema dei tipi, penso che dovrebbe esserci un unit test per quello.

Questa domanda non è simile a questa poiché non affronta lo stesso problema mio. In quella domanda c'è una funzione aziendale (savePeople) e la persona sta chiedendo informazioni sull'implementazione interna (per ogni). Lì dentro c'è un livello di attività centrale (la funzione salva persone) che incapsula il costrutto del linguaggio (per ogni oggetto). Qui il costrutto del linguaggio (enum) è quello usato per specificare il comportamento dal punto di vista aziendale.

In questo caso il dettaglio dell'implementazione coincide con la "vera natura" dei dati, ovvero: un insieme (in senso matematico) di valori. Probabilmente potresti usare un set immutabile, ma gli stessi valori dovrebbero essere ancora presenti lì. Se si utilizza un array, è necessario eseguire la stessa operazione per testare la logica aziendale. Penso che l'enigma qui sia il fatto che il costrutto del linguaggio coincida molto bene con la natura dei dati. Non sono sicuro di essermi spiegato correttamente


19
Come sarebbe esattamente un test unitario di un enum?
jonrsharpe,


@jonrsharpe Asserirebbe che i valori che si trovano all'interno dell'enum sono quelli che ti aspetti. Lo farei ripetendo i valori dell'enum, aggiungendoli a un set, ad esempio come stringhe. Ordina quel set. Il confronto impostato rispetto a un elenco ordinato di valori scritti a mano nel test. Dovrebbero corrispondere.
IS1_SO,

1
@jonrsharpe, mi piace pensare ai test unitari anche come "definizioni" o "requisiti" scritti in codice. Un test unitario di un enum sarebbe semplice come controllare il numero di elementi sull'enum e i loro valori. Specialmente in C #, dove gli enum non sono classi, ma possono essere mappati direttamente su numeri interi, garantirne i valori e non programmarli per coincidenza può rivelarsi utile ai fini della serializzazione.
Machado,

2
IMHO non è molto più utile del test se 2 + 2 = 4 per verificare se l'Universo ha ragione. Si verifica il codice utilizzando quell'enum, non l'enum stesso.
Agent_L

Risposte:


39

Se si dispone di un enum con solo valori (nessun metodo come si potrebbe fare in Java) e questo enum fa parte della definizione aziendale del sistema, si dovrebbe scrivere un test unitario per esso?

No, sono solo stato.

Fondamentalmente, il fatto che stai usando un enum è un dettaglio di implementazione ; questo è il genere di cose che potresti voler essere in grado di riflettere in un design diverso.

Testare la completezza degli enum è analogo al test della presenza di tutti gli interi rappresentabili.

Testare i comportamenti supportati dalle enumerazioni, tuttavia, è una buona idea. In altre parole, se si parte da una suite di test di passaggio e si commenta un singolo valore enum, almeno un test dovrebbe fallire (gli errori di compilazione considerati errori).


5
Ma in questo caso il dettaglio dell'implementazione coincide con la "vera natura" dei dati, cioè: un insieme (in senso matematico) di valori. Probabilmente potresti usare un set immutabile, ma gli stessi valori dovrebbero essere ancora presenti lì. Se si utilizza un array, è necessario eseguire la stessa operazione per testare la logica aziendale. Penso che l'enigma qui sia il fatto che il costrutto del linguaggio coincida molto bene con la natura dei dati. Non sono sicuro di essermi spiegato correttamente.
IS1_SO,

4
@ IS1_SO - al punto che VOU dovrebbe fallire un test: vero? In tal caso, non è stato necessario testare l'Enum in modo specifico. No? Forse questo è un segno che si potrebbe modellare il codice più semplice e creare un'astrazione sopra la 'vera natura' dei dati - ad esempio, a prescindere dalle carte in un mazzo, non si ha realmente bisogno di avere una rappresentazione di [ Hearts, Spades, Diamonds, Clubs] se hai mai carta solo se una carta è rossa / nera?
AnotherDave,

1
@ IS1_SO diciamo che hai un enum di codici di errore e vuoi lanciare un null_ptrerrore. Ora che ha un codice di errore tramite l'enum. Il codice che controlla un null_ptrerrore cerca anche il codice tramite l'enum. Quindi potrebbe avere un valore di 5(ad esempio). Ora devi aggiungere un altro codice di errore. L'enum è cambiato (diciamo che ne aggiungiamo uno nuovo all'inizio dell'enum) Il valore di null_ptrè ora 6. È un problema? ora si restituisce un codice di errore 6e si verifica 6. Finché tutto è logicamente coerente, stai bene, nonostante questo cambiamento rompa il tuo test teorico.
Baldrickk,

17

Non testare una dichiarazione enum . È possibile verificare se l'ingresso / uscita della funzione ha i valori enum previsti. Esempio:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

Lei non si scrivono dei test che verificano poi enum Paritydefinisce i nomi Evene Odd. Un test del genere sarebbe inutile dal momento che ripeteresti ciò che è già indicato dal codice. Dire la stessa cosa due volte non la rende più corretta.

Si fanno prove di scrittura verifica GetParitydiciamo torneranno Evenper 0, Oddper 1 e così via. Questo è utile perché non stai ripetendo il codice, stai verificando il comportamento del codice, indipendentemente dall'implementazione. Se il codice all'interno GetParityfosse completamente riscritto, i test sarebbero comunque validi. In effetti, i principali vantaggi dei test unitari sono la libertà di riscrivere e refactificare il codice in modo sicuro, garantendo che il codice funzioni ancora come previsto.

Ma se si dispone di un test che garantisce che una dichiarazione enum definisce i nomi previsti, qualsiasi modifica apportata all'enum in futuro richiederà la modifica anche del test. Ciò significa che non è solo il doppio del lavoro, ma significa anche perdere qualsiasi beneficio per il test unitario. Se è necessario modificare il codice e testare contemporaneamente , non vi è alcuna protezione contro l'introduzione di bug.


Ho aggiornato la mia domanda nel tentativo di rispondere a questa risposta, verificala per vedere se aiuta.
IS1_SO,

@ IS1_SO: OK, ciò mi confonde: stai generando dinamicamente i valori enum o cosa sta succedendo?
Jacques B

No. Ciò che intendevo dire è che in questo caso il costrutto del linguaggio selezionato per rappresentare i valori è un enum. Ma come sappiamo questo è un dettaglio di implementazione. Cosa succede se si seleziona un array o un Set <> (in Java) o una stringa con alcuni token di separazione per rappresentare i valori? In tal caso, sarebbe logico verificare che i valori contenuti siano quelli di interesse per l'azienda. Questo è il mio punto. Questa spiegazione aiuta?
IS1_SO,

3
@ IS1_SO: Stai parlando di testare un'istanza enum restituita da una funzione ha un certo valore atteso? Perché sì, potresti provarlo. Non devi semplicemente testare la dichiarazione enum stessa.
Jacques B

11

Se c'è il rischio che la modifica dell'enum rompa il tuo codice, sicuramente, qualsiasi cosa con l'attributo [Flags] in C # sarebbe un buon caso perché l'aggiunta di un valore tra 2 e 4 (3) sarebbe un 1 e 2 bit a bit anziché un oggetto discreto.

È uno strato di protezione.

Dovresti considerare di avere un codice di pratica enum con cui tutti gli sviluppatori hanno familiarità. Non fare affidamento su rappresentazioni testuali dell'enum è comune, ma ciò potrebbe essere in conflitto con le linee guida sulla serializzazione.

Ho visto le persone "correggere" la capitalizzazione delle voci enum, ordinarle in ordine alfabetico o in base a un altro raggruppamento logico che ha rotto tutti i frammenti di codice errato.


5
Se i valori numerici dell'enum vengono utilizzati ovunque, ad esempio se archiviati in un database, il riordino (inclusa la rimozione o l'inserimento prima dell'ultimo valore) potrebbe rendere non validi i record esistenti.
stannius,

3
+1, questa risposta è sottovalutata. Se i tuoi enum fanno parte della serializzazione, dell'interfaccia di input con parole esterne o informazioni componibili in modo bit per bit, dovranno sicuramente essere testati per coerenza in ogni versione del sistema. Almeno se sei preoccupato per la retrocompatibilità, che di solito è una buona cosa.
Machado,

11

No, un test che verifica che un enum contenga tutti i valori validi e nient'altro sta essenzialmente ripetendo la dichiarazione dell'enum. Verificherebbe solo che il linguaggio implementa correttamente il costrutto enum che è un test insensato.

Detto questo, dovresti testare il comportamento che dipende dai valori di enum. Ad esempio, se si utilizzano i valori enum per serializzare entità su json o altro, o se si memorizzano i valori in un database, è necessario testare il comportamento di tutti i valori dell'enum. In questo modo, se l'enum viene modificato, almeno uno dei test dovrebbe fallire. In ogni caso, ciò che vorresti testare è il comportamento attorno al tuo enum, non la dichiarazione enum stessa.


3

Il codice dovrebbe funzionare correttamente indipendentemente dai valori effettivi di un enum. In tal caso, non sono necessari test unitari.

Ma potresti avere un codice in cui cambiando un valore enum si romperanno le cose. Ad esempio, se un valore enum è memorizzato in un file esterno e dopo aver modificato il valore enum leggendo il file esterno si otterrà un risultato errato. In quel caso avrai un GRANDE commento vicino all'enum che avvisa chiunque di non modificare alcun valore, e potresti benissimo scrivere un unit test che controlla i valori numerici.


1

In generale, il solo fatto di verificare che un enum abbia un elenco di valori hardcoded non ha molto valore, come hanno detto altre risposte, perché è sufficiente aggiornare test ed enum insieme.

Una volta ho avuto il caso che un modulo usasse i tipi di enumerazione di altri due moduli e li mappasse. (Uno degli enumerati aveva una logica aggiuntiva, l'altro era per l'accesso al DB, entrambi avevano dipendenze che dovrebbero essere isolate l'una dall'altra.)

In questo caso, ho aggiunto un test (nel modulo di mappatura) che verificava che tutte le voci di enum nell'enum di origine esistessero anche nell'enum di destinazione (e quindi che la mappatura avrebbe sempre funzionato). (Per alcuni casi ho anche controllato il contrario.)

In questo modo, quando qualcuno ha aggiunto una voce enum a uno degli enum e ha dimenticato di aggiungere la voce corrispondente a un altro, un test ha iniziato a fallire.


1

Gli enum sono semplicemente dei tipi finiti, con nomi personalizzati (si spera significativi). Un enum potrebbe avere un solo valore, come quello voidche contiene solo null(alcune lingue lo chiamano unite usano il nome voidper un enum senza elementi!). Può avere due valori, come il boolquale ha falsee true. Potrebbe averne tre, come colourChannelcon red, greene 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, resultcontenente win/lose / drawè isomorfo a quanto sopra colourChannel, poiché possiamo sostituire ad esempio colourChannelcon result, redcon win, greencon losee bluecon drawe 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 " colourChanneltest" che abbiamo scritto passerà comunque, anche se non ce n'è colourChannelpiù!

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 rede testiamo solo con red, allora abbiamo una copertura del 100%. Un verificatore di proprietà (tenterà di) generare controesempi alle nostre asserzioni, come la generazione digreenblue 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.

1

No. I test unitari sono per i test unitari.

Nella programmazione orientata agli oggetti, un'unità è spesso un'intera interfaccia, come una classe, ma potrebbe essere un metodo individuale.

https://en.wikipedia.org/wiki/Unit_testing

Un test automatizzato per un enum dichiarato sarebbe testare l'integrità del linguaggio e della piattaforma su cui è in esecuzione piuttosto che della logica nel codice creato dallo sviluppatore. Non servirebbe a nessuno scopo utile - la documentazione inclusa dal momento che il codice che dichiara l'enum funge da documentazione, così come il codice che lo testerebbe.


0

Ci si aspetta che verifichi il comportamento osservabile del codice, gli effetti di chiamate metodo / funzione sullo stato osservabile. Finché il codice fa la cosa giusta che stai bene, non è necessario testare nient'altro.

Non è necessario affermare esplicitamente che un tipo di enum abbia le voci che ci si aspetta, proprio come non si afferma esplicitamente che esiste effettivamente una classe o che ha i metodi e gli attributi previsti.

In realtà testando il comportamento stai implicitamente affermando che esistono classi, metodi e valori coinvolti nel test, quindi non è necessario affermarlo esplicitamente.

Nota che non hai bisogno di nomi significativi per il tuo codice per fare la cosa giusta, è solo una comodità per le persone che leggono il tuo codice. Potresti far funzionare il tuo codice con valori enum come foo, bar... e metodi come frobnicate().

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.