Dovremmo scrivere dei test per i nostri getter e setter o è eccessivo?
Dovremmo scrivere dei test per i nostri getter e setter o è eccessivo?
Risposte:
Direi di no.
@Will ha detto che dovresti puntare al 100% di copertura del codice, ma secondo me è una distrazione pericolosa. È possibile scrivere test unit che hanno una copertura del 100% e tuttavia non testare assolutamente nulla.
I test unitari sono lì per testare il comportamento del tuo codice, in modo espressivo e significativo, e getter / setter sono solo un mezzo per raggiungere un fine. Se test usi i getter / setter per raggiungere il loro obiettivo di testare la funzionalità "reale", allora è abbastanza buono.
Se, d'altra parte, i tuoi getter e setter fanno molto di più che ottenere e impostare (cioè sono metodi adeguatamente complessi), allora sì, dovrebbero essere testati. Ma non scrivere un test unitario solo per testare un getter o setter, è una perdita di tempo.
Roy Osherove nel suo famoso libro "The Art Of Unit Testing" afferma:
Le proprietà (getter / setter in Java) sono buoni esempi di codice che di solito non contiene alcuna logica e non richiede test. Ma fai attenzione: una volta aggiunto qualsiasi segno di spunta all'interno della proprietà, ti consigliamo di verificare che la logica sia in fase di test.
Nota : questa risposta continua a ricevere voti, sebbene potenzialmente un cattivo consiglio. Per capire perché, dai un'occhiata alla sua sorellina qui sotto.
Va bene controverso, ma direi che a chiunque risponda "no" a questa domanda manca un concetto fondamentale di TDD.
Per me, la risposta è un sì clamoroso se segui TDD. Se non lo sei, allora no è una risposta plausibile.
TDD è spesso citato come avere i principali vantaggi.
Come programmatori, è terribilmente allettante pensare agli attributi come a qualcosa di significativo e ai getter e setter come a una sorta di sovraccarico.
Ma gli attributi sono un dettaglio di implementazione, mentre setter e getter sono l'interfaccia contrattuale che fa effettivamente funzionare i programmi.
È molto più importante precisare che un oggetto dovrebbe:
Consenti ai suoi clienti di cambiare il suo stato
e
Consenti ai suoi client di interrogare il suo stato
quindi come viene effettivamente memorizzato questo stato (per il quale un attributo è il modo più comune, ma non l'unico).
Un test come
(The Painter class) should store the provided colour
è importante per la parte relativa alla documentazione di TDD.
Il fatto che l'eventuale implementazione sia banale (attributo) e non porti benefici alla difesa dovrebbe essere ignoto quando si scrive il test.
Uno dei problemi chiave nel mondo dello sviluppo del sistema è la mancanza di ingegneria di andata e ritorno 1 - il processo di sviluppo di un sistema è frammentato in sottoprocessi sconnessi i cui artefatti (documentazione, codice) sono spesso incoerenti.
1 Brodie, Michael L. "John Mylopoulos: cucire semi di modelli concettuali." Modellazione concettuale: fondamenti e applicazioni. Springer Berlin Heidelberg, 2009. 1-9.
È la parte della documentazione di TDD che garantisce che le specifiche del sistema e il suo codice siano sempre coerenti.
All'interno di TDD scriviamo prima i test di accettazione non riusciti, solo poi scriviamo il codice che li lascia passare.
All'interno del BDD di livello superiore, scriviamo prima gli scenari, quindi li facciamo passare.
Perché dovresti escludere setter e getter?
In teoria, all'interno di TDD è perfettamente possibile per una persona scrivere il test e un'altra per implementare il codice che lo fa passare.
Quindi chiediti:
La persona che scrive i test per una classe deve menzionare getter e setter.
Poiché getter e setter sono un'interfaccia pubblica per una classe, la risposta è ovviamente sì , o non ci sarà modo di impostare o interrogare lo stato di un oggetto. Tuttavia , il modo per farlo non è necessariamente testando ogni metodo in modo isolato, vedi la mia altra risposta per ulteriori informazioni.
Ovviamente, se scrivi prima il codice, la risposta potrebbe non essere così chiara.
tl; dr: Sì , dovresti , e con OpenPojo è banale.
Dovresti fare un po 'di validazione nei tuoi getter e setter, quindi dovresti testarlo. Ad esempio, setMom(Person p)
non dovrebbe consentire di impostare qualcuno più giovane di loro come madre.
Anche se non stai facendo nulla di tutto ciò, le probabilità lo farai in futuro, quindi questo sarà un buon metodo per l'analisi di regressione. Se si desidera consentire alle madri di assestarsi su di null
te, è necessario sottoporsi a un test, nel caso in cui qualcuno dovesse cambiarlo in seguito, ciò rafforzerà i tuoi presupposti.
Un bug comune è void setFoo( Object foo ){ foo = foo; }
dove dovrebbe essere void setFoo( Object foo ){ this.foo = foo; }
. (Nel primo caso in foo
cui si sta scrivendo è il parametro non il foo
campo sull'oggetto ).
Se stai restituendo un array o una raccolta, dovresti verificare se il getter eseguirà o meno copie difensive dei dati passati al setter prima di tornare.
Altrimenti, se hai i setter / getter più elementari, i test unitari aggiungeranno forse circa 10 minuti al massimo per oggetto, quindi qual è la perdita? Se aggiungi un comportamento hai già un test scheletro e ottieni questo test di regressione gratuitamente. Se stai usando Java, non hai scuse poiché c'è OpenPojo . Esistono un insieme di regole che è possibile abilitare e quindi scansionare l'intero progetto con loro per assicurarsi che vengano applicate in modo coerente all'interno del codice.
Dai loro esempi :
final PojoValidator pojoValidator = new PojoValidator();
//create rules
pojoValidator.addRule( new NoPublicFieldsRule () );
pojoValidator.addRule( new NoPrimitivesRule () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );
//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester () );
pojoValidator.addTester( new GetterTester () );
//test all the classes
for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) )
pojoValidator.runValidation( pojoClass );
Mi permetta di elaborare:
Da lavorare efficacemente con il codice legacy 1 :
Il termine unit test ha una lunga storia nello sviluppo di software. Comune alla maggior parte delle concezioni di unit test è l'idea che si tratti di test isolati dai singoli componenti del software. Quali sono i componenti? La definizione varia, ma nei test unitari, di solito ci occupiamo delle unità comportamentali più atomiche di un sistema. Nel codice procedurale, le unità sono spesso funzioni. Nel codice orientato agli oggetti, le unità sono classi.
Nota che con OOP, dove trovi getter e setter, l'unità è la classe , non necessariamente singoli metodi .
Tutti i requisiti e i test seguono la forma della logica Hoare :
{P} C {Q}
Dove:
{P}
è il presupposto ( dato )C
è la condizione di trigger ( quando ){Q}
è la postcondizione ( quindi )Quindi arriva la massima:
Comportamento del test, non implementazione
Ciò significa che non dovresti testare il modo in cui C
raggiunge la post-condizione, dovresti verificare che {Q}
sia il risultato C
.
Quando si tratta di OOP, C
è una classe. Quindi non dovresti testare gli effetti interni, ma solo quelli esterni.
Getter e setter possono comportare un po 'di logica, ma così a lungo questa logica non ha effetti esterni - rendendoli accessor di bean 2 ) un test dovrà guardare all'interno dell'oggetto e da ciò non solo violare l'incapsulamento ma anche testare l'implementazione.
Quindi non dovresti testare isolatori e getter di bean. Questo non va bene:
Describe 'LineItem class'
Describe 'setVAT()'
it 'should store the VAT rate'
lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )
Anche se se setVAT
generasse un'eccezione, un test corrispondente sarebbe appropriato poiché ora esiste un effetto esterno.
Non ha praticamente senso cambiare lo stato interno di un oggetto se tale cambiamento non ha alcun effetto all'esterno, anche se tale effetto si verifica in seguito.
Quindi un test per setter e getter dovrebbe riguardare l'effetto esterno di questi metodi, non quelli interni.
Per esempio:
Describe 'LineItem class'
Describe 'getGross()'
it 'should return the net time the VAT'
lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )
Puoi pensare a te stesso:
Aspetta un secondo, stiamo testando
getGross()
qui nonsetVAT()
.
Ma se il setVAT()
malfunzionamento di quel test dovesse fallire lo stesso.
1 Feathers, M., 2004. Funziona efficacemente con il codice legacy. Prentice Hall Professional.
2 Martin, RC, 2009. Codice pulito: un manuale di agile software artigiano. Pearson Education.
Sebbene vi siano ragioni giustificate per Proprietà, esiste una convinzione comune di progettazione orientata agli oggetti secondo cui esporre lo stato membro tramite Proprietà non è una buona progettazione. L'articolo di Robert Martin sull'Open Closed Principle si espande su questo affermando che Proprietà incoraggia l'accoppiamento e quindi limita la possibilità di chiudere una classe dalla modifica - se si modifica la proprietà, anche tutti i consumatori della classe dovranno cambiare. Si qualifica che esporre le variabili dei membri non è necessariamente una cattiva progettazione, potrebbe essere solo uno stile scadente. Tuttavia, se le proprietà sono di sola lettura, ci sono meno possibilità di abuso ed effetti collaterali.
L'approccio migliore che posso fornire per i test unitari (e questo può sembrare strano) è quello di rendere il maggior numero possibile di proprietà protette o interne. Ciò impedirà l'accoppiamento scoraggiando la scrittura di test sciocchi per getter e setter.
Esistono ovvi motivi per cui è necessario utilizzare le proprietà di lettura / scrittura, come le proprietà ViewModel associate a campi di input, ecc.
Più praticamente, i test unitari dovrebbero guidare la funzionalità attraverso metodi pubblici. Se il codice che stai testando utilizza tali proprietà, ottieni la copertura del codice gratuitamente. Se si scopre che queste proprietà non vengono mai evidenziate dalla copertura del codice, c'è una forte possibilità che:
Se si scrivono test per getter e setter, si ottiene un falso senso di copertura e non si sarà in grado di determinare se le proprietà sono effettivamente utilizzate dal comportamento funzionale.
Se la complessità ciclomatica del getter e / o del setter è 1 (che di solito sono), allora la risposta è no, non dovresti.
Quindi, a meno che tu non abbia uno SLA che richiede una copertura del codice al 100%, non preoccuparti e concentrati sul test dell'aspetto importante del tuo software.
PS Ricorda di differenziare getter e setter, anche in lingue come C # dove le proprietà potrebbero sembrare la stessa cosa. La complessità del setter può essere superiore al getter e quindi validare un test unitario.
Una versione umoristica, ma saggia: The Way of Testivus
"Scrivi il test che puoi oggi"
Testare getter / setter può essere eccessivo se sei un tester esperto e questo è un piccolo progetto. Tuttavia, se hai appena iniziato a imparare come unit test o questi getter / setter possono contenere una logica (come nell'esempio di @ ArtB setMom()
), sarebbe una buona idea scrivere test.
Questo è stato in realtà un argomento recente tra la mia squadra e I. Giriamo per l'80% di copertura del codice. Il mio team sostiene che getter e setter vengono implementati automaticamente e il compilatore sta generando un codice di base dietro le quinte. In questo caso, dato che il codice generato non è invadente, non ha molto senso testare il codice che il compilatore crea per te. Abbiamo anche discusso di metodi asincroni e in quel caso il compilatore genera un sacco di codice dietro le quinte. Questo è un caso diverso e qualcosa che testiamo. Risposta lunga breve, portalo con il tuo team e decidi tu stesso se vale la pena testarlo.
Inoltre, se stai utilizzando il rapporto sulla copertura del codice come noi, una cosa che puoi fare è aggiungere l'attributo [ExcludeFromCodeCoverage]. La nostra soluzione è stata quella di usarlo per i modelli che hanno solo proprietà usando getter e setter o sulla proprietà stessa. In questo modo non influirà sulla% di copertura totale del codice quando viene eseguito il report di copertura del codice, supponendo che sia quello che si sta utilizzando per calcolare le percentuali di copertura del codice. Buon test!
Secondo me la copertura del codice è un buon modo per vedere se hai perso qualche funzionalità che dovresti coprire.
Quando si controlla manualmente la copertura con la sua bella colorazione, si può affermare che non è necessario testare semplici getter e setter (anche se lo faccio sempre).
Quando controlli solo la percentuale di copertura del codice sul tuo progetto, una percentuale di copertura del test come l'80% non ha senso. Puoi testare tutte le parti logiche nessuna e dimenticare alcune parti cruciali. In questo caso solo il 100% significa che hai testato tutto il tuo codice vitale (e anche tutto il codice non logico). Non appena è del 99,9% sai che hai dimenticato qualcosa.
A proposito: la copertura del codice è il controllo finale per vedere se hai (completamente) testato una classe. Ma la copertura del codice al 100% non significa necessariamente che tu abbia effettivamente testato tutte le funzionalità della classe. Pertanto, i test unitari devono essere sempre implementati seguendo la logica della classe. Alla fine esegui la copertura per vedere se hai dimenticato qualcosa. Quando l'hai fatto bene, hai raggiunto il 100% la prima volta.
Ancora una cosa: mentre recentemente lavoravo in una grande banca nei Paesi Bassi, ho notato che Sonar indicava una copertura del codice del 100%. Tuttavia, sapevo che mancava qualcosa. Ispezionando le percentuali di copertura del codice per file, è stato indicato un file con una percentuale inferiore. L'intera percentuale di base di codice era così grande che il singolo file non faceva visualizzare la percentuale come 99,9%. Quindi potresti voler cercare questo ...
Ho fatto una piccola analisi della copertura ottenuta nel codice JUnit stesso .
Una categoria di codice scoperto è "troppo semplice per essere testata" . Ciò include semplici getter e setter, che gli sviluppatori di JUnit non testano.
D'altra parte, JUnit non ha alcun metodo (non deprecato) più lungo di 3 righe che non è coperto da alcun test.
Direi: SÌ Gli errori nei metodi getter / setter possono insinuarsi silenziosamente e causare alcuni brutti bug.
Ho scritto una libreria per semplificare questo e altri test. L'unica cosa che devi scrivere nei test JUnit è questa:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
Sì, soprattutto se l'elemento da ottenere è un oggetto di una classe sottoclassata da una classe astratta. Il tuo IDE può avvisarti o meno che una determinata proprietà non è stata inizializzata.
E poi alcuni test apparentemente non correlati si interrompono con un NullPointerException
e ci vuole un po 'di tempo per capire che una proprietà gettabile non è effettivamente lì per arrivare in primo luogo.
Anche se non sarebbe comunque così male come scoprire il problema in produzione.
Potrebbe essere una buona idea assicurarsi che tutte le tue classi astratte abbiano costruttori. In caso contrario, il test di un getter potrebbe avvisarti di un problema lì.
Per quanto riguarda getter e setter di primitivi, la domanda potrebbe essere: sto testando il mio programma o sto testando JVM o CLR? In generale, non è necessario testare la JVM.