I test unitari dovrebbero essere scritti per getter e setter?

Dovremmo scrivere test per i nostri getter e setter o è eccessivo?

Direi di no

@Sarà detto che dovresti mirare alla copertura del codice al 100%, ma a mio parere è una distrazione pericolosa. È ansible scrivere test unitari con copertura al 100%, ma non testare assolutamente nulla.

I test unitari sono lì per testare il comportamento del tuo codice, in un modo espressivo e significativo, e i getter / setter sono solo un mezzo per raggiungere un fine. Se esegui test usa i getter / setter per raggiungere il loro objective di testare la funzionalità “reale”, allora è abbastanza buono.

Se, al contrario, i tuoi getter e setter fanno molto di più che ottenere e impostare (cioè sono metodi correttamente complessi), allora sì, dovrebbero essere testati. Ma non scrivere un caso di test unitario solo per testare un getter o setter, è una perdita di tempo.

Roy Osherove nel suo famoso libro ‘The Art Of Unit Testing’ dice:

Le proprietà (getter / setter in Java) sono buoni esempi di codice che di solito non contengono alcuna logica e non richiedono test. Ma attenzione: una volta aggiunto qualsiasi assegno all’interno della proprietà, dovrai assicurarti che la logica sia in fase di test.

Un sonoro SÌ con TDD


Nota : questa risposta continua a far salire i voti, anche se potenzialmente un cattivo consiglio. Per capire perché, dai un’occhiata alla sua sorellina .

Se sei qui per rafforzare le tue convinzioni – a tutti piacciono i rinforzi. Ma se vuoi essere più saggio, considera la sorella.


Controverso, ma direi che chiunque risponda “no” a questa domanda manca un concetto fondamentale di TDD.

Per me, la risposta è un sonoro se segui TDD. Se non lo sei, allora no è una risposta plausibile.

Il DDD in TDD

Il TDD è spesso citato come avente i principali vantaggi.

  • Difesa
    • Garantire che il codice possa cambiare ma non il suo comportamento .
    • Ciò consente la sempre più importante pratica del refactoring .
    • Ottieni questo TDD o no.
  • Design
    • Tu specifichi cosa dovrebbe fare qualcosa, come dovrebbe comportarsi prima di implementarlo .
    • Questo spesso significa decisioni di implementazione più consapevoli.
  • Documentazione
    • La suite di test dovrebbe servire come documentazione delle specifiche (requisiti).
    • L’uso di test per tale scopo significa che la documentazione e l’implementazione sono sempre in uno stato coerente: una modifica a una significa una modifica ad un’altra. Confronta con i requisiti di mantenimento e la progettazione su un documento separato.

Separare la responsabilità dall’implementazione

Come programmatori, è terribilmente allettante pensare agli attributi come a qualcosa di significativo, a 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 effettivamente fa funzionare i programmi.

È molto più importante sillabare che un object dovrebbe:

Consenti ai suoi clienti di cambiare il suo stato

e

Permetti ai suoi clienti di interrogare il suo stato

quindi come viene effettivamente memorizzato questo stato (per il quale un attributo è il più comune, ma non l’unico modo).

Un test come

(The Painter class) should store the provided colour 

è importante per la parte di documentazione di TDD.

Il fatto che l’eventuale implementazione sia banale (attributo) e non porti alcun vantaggio in termini di difesa dovrebbe essere a te sconosciuto quando scrivi il test.

La mancanza di ingegneria di andata e ritorno …

Uno dei problemi chiave nel mondo dello sviluppo del sistema è la mancanza dell’ingegneria round-trip 1 : il processo di sviluppo di un sistema è frammentato in sub-processi sconnessi i cui artefatti (documentazione, codice) sono spesso incoerenti.

1 Brodie, Michael L. “John Mylopoulos: semi di cucito della modellazione concettuale.” Modellazione concettuale: fondamenti e applicazioni. Springer Berlin Heidelberg, 2009. 1-9.

… e come TDD lo risolve

È la parte della documentazione di TDD che garantisce che le specifiche del sistema e il suo codice siano sempre coerenti.

Progettare prima, implementare più tardi

All’interno di TDD scriviamo per primi il test di accettazione fallito, solo poi scriviamo il codice che consente il loro passaggio.

All’interno del BDD di livello superiore, scriviamo prima gli scenari, poi li facciamo passare.

Perché dovresti escludere setter e getter?

In teoria, è perfettamente ansible all’interno di TDD che una persona scriva il test e un altro per implementare il codice che lo fa passare.

Quindi chiediti:

Se la persona che scrive i test per una class menziona getter e setter.

Poiché getter e setter sono un’interfaccia pubblica per una class, la risposta è ovviamente , o non ci sarà modo di impostare o interrogare lo stato di un object. Tuttavia , il modo per farlo non è necessariamente testando ciascun metodo in isolamento, vedere la mia altra risposta per ulteriori informazioni.

Ovviamente, se scrivi prima il codice, la risposta potrebbe non essere così chiara.

tl; dr: , dovresti , e con OpenPojo è banale.

  1. Dovresti fare qualche convalida nei tuoi getter e setter, quindi dovresti provarlo. Ad esempio, setMom(Person p) non dovrebbe consentire di impostare qualcuno più giovane di loro come madre.

  2. Anche se non stai facendo nulla del genere ora, le probabilità sono che lo farai in futuro, quindi questo sarà un buon aiuto per l’analisi di regressione. Se vuoi consentire di impostare le madri a null , dovresti fare un test per questo se qualcuno dovesse cambiarlo più tardi, questo rafforzerà le tue ipotesi.

  3. Un bug comune è void setFoo( Object foo ){ foo = foo; } void setFoo( Object foo ){ foo = foo; } dove dovrebbe essere void setFoo( Object foo ){ this.foo = foo; } void setFoo( Object foo ){ this.foo = foo; } . (Nel primo caso il foo quale si sta scrivendo è il parametro non il campo foo sull’object ).

  4. Se stai restituendo un array o una raccolta, dovresti verificare se il getter sta per eseguire copie difensive dei dati passati al setter prima di tornare.

  5. Altrimenti, se hai i setter / getter più basici, i test unitari aggiungeranno forse circa 10 minuti al massimo per object, quindi qual è la perdita? Se aggiungi comportamenti, hai già un test di scheletro e ottieni questo test di regressione gratuitamente. Se stai usando Java, non hai scuse dato che c’è OpenPojo . Esistono già un set di regole che è ansible abilitare e quindi eseguire la scansione dell’intero progetto con loro per assicurarsi che siano 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 classs for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) ) pojoValidator.runValidation( pojoClass ); 

Sì, ma non sempre in isolamento

Consentitemi di elaborare:

Cos’è un test unitario?

Dal funzionamento efficace con il codice legacy 1 :

Il termine unit test ha una lunga storia nello sviluppo del software. Comune alla maggior parte delle concezioni dei test unitari è l’idea che siano test in isolamento dei 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 class , non necessariamente i singoli metodi .

Che cos’è un buon test?

Tutti i requisiti e i test seguono la forma della logica Hoare :

{P} C {Q}

Dove:

  • {P} è la precondizione ( data )
  • C è la condizione di innesco ( quando )
  • {Q} è la post-condizione ( quindi )

Quindi arriva la massima:

Comportamento del test, non implementazione

Ciò significa che non dovresti testare come C raggiunge la post-condizione, dovresti controllare che {Q} sia il risultato di C

Quando si tratta di OOP, C è una class. Quindi non dovresti testare effetti interni, solo effetti esterni.

Perché non testare getter e setter di fagioli in isolamento

Getter e setter possono implicare qualche logica, ma per tanto tempo questa logica non ha effetto esterno – rendendole accessorie ai bean 2 ) un test dovrà guardare all’interno dell’object e non solo violare l’incapsulamento ma anche testare l’implementazione.

Quindi non dovresti testare getter e setter di fagioli da soli. 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 ) 

Sebbene se setVAT un’eccezione, un test corrispondente sarebbe appropriato poiché ora c’è un effetto esterno.

Come dovresti testare getter e setter?

Non c’è praticamente alcun punto che modifica lo stato interno di un object se tale cambiamento non ha alcun effetto sull’esterno, anche se tale effetto viene 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 ) 

Potresti pensare a te stesso:

Aspetta un attimo, stiamo testando getGross() qui non setVAT() .

Ma se setVAT() malfunzionamento, il test dovrebbe fallire lo stesso.

1 Feathers, M., 2004. Lavorando efficacemente con il codice legacy. Prentice Hall Professional.

2 Martin, RC, 2009. Codice pulito: un manuale di abilità software agile. Pearson Education.

Se la complessità ciclomatica del getter e / o setter è 1 (che di solito sono), allora la risposta è no, non dovresti.

Quindi, a meno che tu non abbia uno SLA che richiede copertura del codice al 100%, non preoccuparti e concentrati sulla verifica dell’aspetto importante del tuo software.

PS Ricorda di differenziare getter e setter, anche in linguaggi come C #, dove le proprietà potrebbero sembrare la stessa cosa. La complessità del setter può essere maggiore del getter e quindi convalidare un test unitario.

Sebbene esistano ragioni giustificate per le Proprietà, esiste una credenza comune di progettazione orientata agli oggetti che l’esposizione dello stato membro tramite Proprietà è una ctriggers progettazione. L’articolo di Robert Martin sull’Open Closed Principle si espande affermando che le Proprietà incoraggiano l’accoppiamento e quindi limitano la possibilità di chiudere una class dalla modifica – se modifichi la proprietà, anche tutti i consumatori della class dovranno cambiare. Egli ritiene che l’esposizione delle variabili dei membri non sia necessariamente una ctriggers progettazione, ma potrebbe essere semplicemente di cattivo gusto. Tuttavia, se le proprietà sono di sola lettura, ci sono meno possibilità di abuso ed effetti collaterali.

L’approccio migliore che posso fornire per il test delle unità (e questo può sembrare strano) è quello di rendere il maggior numero ansible di proprietà protette o interne. Ciò impedirà l’accoppiamento scoraggiando la scrittura di test stupidi per getter e setter.

Ci sono ovvi motivi per cui dovrebbero essere usate le proprietà di lettura / scrittura, come le proprietà ViewModel associate ai campi di input, ecc.

Più in pratica, i test unitari dovrebbero guidare la funzionalità attraverso metodi pubblici. Se il codice che stai testando utilizza queste proprietà, ottieni la copertura del codice gratuitamente. Se si scopre che queste proprietà non vengono mai evidenziate dalla copertura del codice, esiste una possibilità molto forte che:

  1. Mancano test che utilizzano indirettamente le proprietà
  2. Le proprietà non sono utilizzate

Se si scrivono test per getter e setter, si ottiene un falso senso di copertura e non sarà ansible determinare se le proprietà sono effettivamente utilizzate dal comportamento funzionale.

Una presa divertente, ma saggia: la Via di Testivus

“Scrivi il test che puoi oggi”

Testare i getter / setter può essere eccessivo se sei un tester esperto e questo è un piccolo progetto. Tuttavia, se hai appena iniziato a imparare come test unitario o questi getter / setter possono contenere logica (come l’esempio setMom() di setMom() ), sarebbe una buona idea scrivere test.

Ho fatto una piccola analisi della copertura raggiunta nel codice JUnit stesso .

Una categoria di codice scoperto è “troppo semplice da testare” . 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.

Questo è stato un argomento recente tra il mio team e I. Abbiamo girato per l’80% di copertura del codice. Il mio team sostiene che i getter ei setter sono implementati automaticamente e il compilatore sta generando un codice base dietro le quinte. In questo caso, dato il codice generato non è intrusivo, non ha senso testare il codice che il compilatore crea per te. Abbiamo anche avuto questa discussione sui metodi asincroni e in tal caso il compilatore genera un sacco di codice dietro le quinte. Questo è un caso diverso e qualcosa che testiamo. Risposta lunga insum, fai rapporto con la tua squadra e decidi tu stesso se vale la pena di provarla.

Inoltre, se stai utilizzando il report sulla copertura del codice come noi, una cosa che puoi fare è aggiungere l’attributo [ExcludeFromCodeCoverage]. La nostra soluzione è stata quella di utilizzare questo per i modelli che hanno proprietà solo con getter e setter o sulla proprietà stessa. In questo modo non influirà sulla percentuale di copertura del codice totale quando viene eseguito il report sulla copertura del codice, supponendo che sia ciò che si sta utilizzando per calcolare le percentuali di copertura del codice. Test felici!

A mio parere, la copertura del codice è un buon modo per vedere se hai perso qualsiasi funzionalità che dovresti coprire.

Quando ispezionate la copertura manualmente è piuttosto colorante, quindi si può sostenere che non è necessario testare getter e setter semplici (anche se lo faccio sempre).

Quando si controlla solo la percentuale di copertura del codice sul progetto, una percentuale di copertura del test come l’80% non ha senso. Puoi testare tutte le parti logiche 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 è il 99,9%, sai che hai dimenticato qualcosa.

A proposito: la copertura del codice è il controllo finale per vedere se hai completamente (unità) testato una class. Ma la copertura del codice al 100% non significa necessariamente che hai effettivamente testato tutte le funzionalità della class. Quindi i test unitari dovrebbero sempre essere implementati seguendo la logica della class. Alla fine esegui la copertura per vedere se hai dimenticato qualcosa. Quando hai fatto bene, premi il 100% la prima volta.

Un’altra cosa: mentre di recente lavoravo in una grande banca in Olanda ho notato che Sonar indicava una copertura del 100% del codice. Tuttavia, sapevo che mancava qualcosa. Ispezionando le percentuali di copertura del codice per file indicava un file a una percentuale inferiore. L’intera percentuale del codice base era così grande che il file non rendeva la percentuale visualizzata al 99,9%. Quindi potresti voler guardare questo …