Devo evitare i metodi privati ​​se eseguo TDD?


100

Sto imparando solo TDD. Comprendo che i metodi privati ​​non sono verificabili e non dovrebbero essere preoccupati perché l'API pubblica fornirà informazioni sufficienti per verificare l'integrità di un oggetto.

Ho capito OOP per un po '. Comprendo che i metodi privati ​​rendono gli oggetti più incapsulati, quindi più resistenti ai cambiamenti e agli errori. Pertanto, dovrebbero essere utilizzati per impostazione predefinita e solo quei metodi che contano per i clienti dovrebbero essere resi pubblici.

Bene, è possibile per me creare un oggetto che ha solo metodi privati ​​e interagisce con altri oggetti ascoltando i loro eventi. Questo sarebbe molto incapsulato, ma completamente non verificabile.

Inoltre, è considerata una cattiva pratica aggiungere metodi a scopo di test.

Questo significa che TDD è in contrasto con l'incapsulamento? Qual è il giusto equilibrio? Sono propenso a rendere pubblici la maggior parte o tutti i miei metodi ora ...


9
Le cattive pratiche e la realtà nell'industria del software sono animali diversi. Una situazione ideale è spesso una realtà distorta nel mondo degli affari. Fai ciò che ha senso e seguilo durante l'applicazione. Preferirei avere una cattiva pratica in atto piuttosto che il sapore del mese diffuso in tutta l'applicazione.
Aaron McIver,

10
"i metodi privati ​​non sono verificabili"? Quale lingua? In alcune lingue è scomodo. In altre lingue è perfettamente semplice. Inoltre, stai dicendo che il principio progettuale dell'incapsulamento deve sempre essere implementato con molti metodi privati? Sembra un po 'estremo. Alcune lingue non hanno metodi privati, tuttavia, sembrano ancora avere disegni incapsulati in modo gradevole.
S. Lott

"Comprendo che i metodi privati ​​rendono gli oggetti più incapsulati, quindi più resistenti ai cambiamenti e agli errori. Pertanto, dovrebbero essere utilizzati per impostazione predefinita e solo quei metodi che contano per i clienti dovrebbero essere resi pubblici." Questo mi sembra un contrappunto di ciò che TDD sta cercando di ottenere. TDD è una metodologia di sviluppo che ti porta a creare un design semplice, praticabile e aperto ai cambiamenti. Guardare "da privato" e "solo pubblicizzare ..." è completamente cambiato. Dimentica che esiste un metodo privato per abbracciare TDD. Più tardi, eseguili secondo necessità; come parte del refactoring.
citato il


Quindi @gnat pensi che questo dovrebbe essere chiuso come un duplicato della domanda che è nata dalla mia risposta a questa domanda? * 8 ')
Mark Booth

Risposte:


50

Preferisci test all'interfaccia piuttosto che test sull'implementazione.

Comprendo che i metodi privati ​​non sono verificabili

Questo dipende dal tuo ambiente di sviluppo, vedi sotto.

[metodi privati] non dovrebbero essere preoccupati perché l'API pubblica fornirà informazioni sufficienti per verificare l'integrità di un oggetto.

Esatto, TDD si concentra sul test dell'interfaccia.

I metodi privati ​​sono dettagli di implementazione che potrebbero cambiare durante qualsiasi ciclo di rifattoriale. Dovrebbe essere possibile ricodificare senza modificare l'interfaccia o il comportamento della scatola nera . In effetti, ciò fa parte del vantaggio di TDD, la facilità con cui è possibile generare la sicurezza che le modifiche interne a una classe non influiranno sugli utenti di quella classe.

Bene, è possibile per me creare un oggetto che ha solo metodi privati ​​e interagisce con altri oggetti ascoltando i loro eventi. Questo sarebbe molto incapsulato, ma completamente non verificabile.

Anche se la classe non ha metodi pubblici, i gestori di eventi sono l' interfaccia pubblica ed è contro quell'interfaccia pubblica che puoi testare.

Poiché gli eventi sono l'interfaccia, sono gli eventi che dovrai generare per testare quell'oggetto.

Cerca di usare oggetti finti come colla per il tuo sistema di test. Dovrebbe essere possibile creare un semplice oggetto simulato che genera un evento e rileva il conseguente cambio di stato (possibile tramite un altro oggetto simulato del ricevitore).

Inoltre, è considerata una cattiva pratica aggiungere metodi a scopo di test.

Assolutamente, dovresti essere molto cauto nell'esporre lo stato interno.

Questo significa che TDD è in contrasto con l'incapsulamento? Qual è il giusto equilibrio?

Assolutamente no.

TDD non dovrebbe cambiare l'implementazione delle tue classi se non forse per semplificarle (applicando YAGNI da un punto precedente).

Le migliori pratiche con TDD sono identiche alle migliori pratiche senza TDD, basta scoprire perché prima, perché stai usando l'interfaccia mentre la sviluppi.

Sono propenso a rendere pubblici la maggior parte o tutti i miei metodi ora ...

Questo sarebbe piuttosto buttare via il bambino con l'acqua del bagno.

Non è necessario rendere pubblici tutti i metodi in modo da poterli sviluppare in modo TDD. Vedi le mie note di seguito per vedere se i tuoi metodi privati ​​non sono realmente verificabili.

Uno sguardo più dettagliato al test di metodi privati

Se devi assolutamente provare unitamente alcuni comportamenti privati ​​di una classe, a seconda della lingua / ambiente, potresti avere tre opzioni:

  1. Inserisci i test nella classe che desideri testare.
  2. Inserisci i test in un altro file di classe / sorgente ed esponi i metodi privati ​​che desideri testare come metodi pubblici.
  3. Utilizzare un ambiente di test che consenta di mantenere separato il codice di test e di produzione, consentendo comunque l'accesso al codice di test ai metodi privati ​​del codice di produzione.

Ovviamente la terza opzione è di gran lunga la migliore.

1) Metti i test nella classe che vuoi testare (non ideale)

Memorizzare i casi di test nello stesso file di classe / sorgente del codice di produzione sotto test è l'opzione più semplice. Ma senza molte direttive o annotazioni pre-processore finirai con il tuo codice di test che gonfia inutilmente il tuo codice di produzione e, a seconda di come hai strutturato il tuo codice, potresti finire per esporre accidentalmente l'implementazione interna agli utenti di quel codice.

2) Esporre i metodi privati ​​che si desidera testare come metodi pubblici (davvero non è una buona idea)

Come suggerito, questa è una pratica molto scadente, distrugge l'incapsulamento e esporrà l'implementazione interna agli utenti del codice.

3) Utilizzare un ambiente di test migliore (opzione migliore, se disponibile)

Nel mondo Eclipse, 3. può essere ottenuto usando frammenti . Nel mondo C #, potremmo usare classi parziali . Altre lingue / ambienti hanno spesso funzionalità simili, devi solo trovarle.

Supponendo ciecamente 1. o 2. sono le uniche opzioni che potrebbero comportare un software di produzione gonfio con codice di test o interfacce di classe sgradevoli che lavano la biancheria sporca in pubblico. * 8' )

  • Tutto sommato, è molto meglio non testare contro l'implementazione privata.

5
Non sono sicuro che sarei d'accordo con una delle tre opzioni che suggerisci. La mia preferenza sarebbe quella di testare solo l'interfaccia pubblica come hai detto prima, ma assicurandomi che in tal modo vengano esercitati metodi privati. Parte del vantaggio nel farlo è trovare il codice morto, che è improbabile che accada se si forza il codice di prova a interrompere il normale utilizzo della lingua.
Magus,

I tuoi metodi dovrebbero fare una cosa e un test non dovrebbe considerare l'implementazione in alcun modo. I metodi privati ​​sono dettagli di implementazione. Se testare solo metodi pubblici significa che i test sono test di integrazione, hai un problema di progettazione.
Magus

Che dire di rendere il metodo predefinito / protetto e creare un test in un progetto di test con lo stesso pacchetto?
RichardCypher,

@RichardCypher Questo è effettivamente lo stesso di 2) poiché stai modificando le specifiche del tuo metodo idealmente per compensare una carenza nel tuo ambiente di test, quindi sicuramente ancora scarsa pratica.
Mark Booth,

75

Ovviamente puoi avere metodi privati ​​e ovviamente puoi provarli.

O c'è un modo per far funzionare il metodo privato, nel qual caso puoi provarlo in quel modo, o non c'è modo di far funzionare il privato, nel qual caso: perché diamine stai provando a provarlo, solo elimina la dannata cosa!

Nel tuo esempio:

Bene, è possibile per me creare un oggetto che ha solo metodi privati ​​e interagisce con altri oggetti ascoltando i loro eventi. Questo sarebbe molto incapsulato, ma completamente non verificabile.

Perché non sarebbe testabile? Se il metodo viene invocato in risposta a un evento, fare in modo che il test dia all'oggetto un evento appropriato.

Non si tratta di non avere metodi privati, non di rompere l'incapsulamento. Puoi avere metodi privati ​​ma dovresti testarli attraverso l'API pubblica. Se l'API pubblica si basa su eventi, utilizzare gli eventi.

Per il caso più comune di metodi di supporto privati, possono essere testati attraverso i metodi pubblici che li chiamano. In particolare, poiché ti è permesso solo scrivere codice per far passare un test non riuscito e i tuoi test stanno testando l'API pubblica, tutto il nuovo codice che scrivi sarà di solito pubblico. I metodi privati ​​vengono visualizzati come risultato di un refactoring del metodo di estrazione , quando vengono estratti da un metodo pubblico già esistente. Ma in quel caso, il test originale che verifica il metodo pubblico copre ancora anche il metodo privato, poiché il metodo pubblico chiama il metodo privato.

Quindi, di solito i metodi privati ​​compaiono solo quando sono estratti da metodi pubblici già testati e sono quindi già testati.


3
Il test con metodi pubblici funziona bene il 99% delle volte. La sfida è l'1% delle volte in cui il tuo metodo pubblico singolo ha diverse centinaia o migliaia di righe di codice complesso dietro di esso e tutti gli stati intermedi sono specifici dell'implementazione. Una volta che diventa abbastanza complesso, provare a colpire tutti i casi limite dal metodo pubblico diventa nel migliore dei casi doloroso. In alternativa, testare i casi limite interrompendo l'incapsulamento ed esponendo più metodi come privati, oppure usando un kludge per fare in modo che i test chiamino metodi privati ​​si traducono direttamente in casi di test fragili oltre ad essere brutti.
Dan Neely,

24
Metodi privati ​​grandi e complessi sono un odore di codice. Un'implementazione così complessa da non poter essere scomposta utilmente in parti componenti (con interfacce pubbliche) è un problema di testabilità che rivela potenziali problemi di progettazione e architettura. L'1% dei casi in cui il codice privato è enorme di solito trarrà beneficio dalla rielaborazione per decomporre ed esporre.
S. Lott

13
@Dan Neely codice come questo è abbastanza non verificabile a prescindere - e parte della scrittura di unit test lo sta sottolineando. Eliminare gli stati, suddividere le classi, applicare tutti i refactoring tipici e quindi scrivere unit test. Anche con TDD come sei arrivato a quel punto? Questo è uno dei vantaggi di TDD, la scrittura di codice testabile diventa automatica.
Bill K,

Almeno internalmetodi o metodi pubblici nelle internalclassi dovrebbero essere testati direttamente abbastanza spesso. Fortunatamente .net supporta InternalsVisibleToAttribute, ma senza di esso, testare quel metodo sarebbe un PITA.
CodesInChaos,

25

Quando crei una nuova classe nel tuo codice, lo fai per rispondere ad alcuni requisiti. I requisiti indicano cosa deve fare il codice, non come . Questo rende facile capire perché la maggior parte dei test avvenga a livello di metodi pubblici.

Attraverso i test, verifichiamo che il codice fa quello che dovrebbe fare , genera opportune eccezioni quando previsto, ecc. Non ci interessa davvero come il codice viene implementato dallo sviluppatore. Anche se non ci interessa l'implementazione, ovvero come il codice fa quello che fa, ha senso evitare di testare metodi privati.

Per quanto riguarda le lezioni di test che non hanno metodi pubblici e interagiscono con il mondo esterno solo attraverso eventi, è possibile testare anche questo inviando, attraverso test, gli eventi e ascoltando la risposta. Ad esempio, se una classe deve salvare un file di registro ogni volta che riceve un evento, l'unità test invierà l'evento e verificherà che il file di registro sia stato scritto.

Ultimo ma non meno importante, in alcuni casi è perfettamente valido testare metodi privati. Ecco perché, ad esempio, in .NET, puoi testare non solo le classi pubbliche, ma anche quelle private, anche se la soluzione non è così semplice come per i metodi pubblici.


4
+1 Una caratteristica importante di TDD è che ti costringe a verificare che i REQUISITI siano soddisfatti, piuttosto che verificare che METODI facciano quello che pensano di fare. Quindi la domanda "Posso testare un metodo privato" è un po 'contraria allo spirito di TDD - la domanda potrebbe invece essere "Posso testare un requisito la cui implementazione include metodi privati". E la risposta a questa domanda è chiaramente sì.
Dawood ibn Kareem,

6

Comprendo che i metodi privati ​​non sono verificabili

Non sono d'accordo con questa affermazione, o direi che non testare direttamente i metodi privati . Un metodo pubblico può chiamare diversi metodi privati. Forse l'autore voleva avere metodi "piccoli" ed estrarre parte del codice in un metodo privato abilmente chiamato.

Indipendentemente da come viene scritto il metodo pubblico, il codice di test dovrebbe coprire tutti i percorsi. Se, dopo i test, scopri che una delle istruzioni della filiale (if / switch) in un metodo privato non è mai stata coperta nei test, allora hai un problema. O hai perso un caso e l'implementazione è corretta OPPURE l'implementazione è sbagliata e quel ramo in realtà non dovrebbe mai esistere.

Questo è il motivo per cui uso molto Cobertura e NCover, per assicurarmi che il mio test del metodo pubblico copra anche i metodi privati. Sentiti libero di scrivere buoni oggetti OO con metodi privati ​​e non lasciare che TDD / Test vadano sulla tua strada in tale materia.


5

Il tuo esempio è ancora perfettamente testabile finché usi Dependency Injection per fornire le istanze con cui il tuo CUT interagisce. Quindi è possibile utilizzare un finto, generare gli eventi di interesse e quindi osservare se il CUT esegue o meno le azioni corrette sulle sue dipendenze.

D'altra parte, se hai una lingua con un buon supporto per gli eventi, potresti prendere una strada leggermente diversa. Non mi piace quando gli oggetti si iscrivono agli eventi stessi, invece hanno la fabbrica che crea l'oggetto che collega gli eventi ai metodi pubblici dell'oggetto. È più facile da testare e rende esternamente visibile per quali tipi di eventi il ​​CUT deve essere testato.


È una grande idea ... "... avere la fabbrica che crea l'oggetto collegando gli eventi ai metodi pubblici dell'oggetto. È più facile da testare e lo rende visibile esternamente per quali tipi di eventi il ​​CUT deve essere testato. "
cucciolo

5

Non dovresti aver bisogno di abbandonare usando metodi privati. È perfettamente ragionevole usarli, ma dal punto di vista dei test è più difficile testarli direttamente senza interrompere l'incapsulamento o aggiungere codice specifico del test alle classi. Il trucco è ridurre al minimo le cose che sai che faranno vacillare il tuo istinto perché ti senti come se avessi sporcato il tuo codice.

Queste sono le cose che tengo a mente per cercare di raggiungere un equilibrio praticabile.

  1. Ridurre al minimo il numero di metodi e proprietà privati ​​utilizzati. La maggior parte delle cose che devi fare in classe tende ad essere comunque esposta pubblicamente, quindi pensa a se hai davvero bisogno di rendere privato quel metodo intelligente.
  2. Riduci al minimo la quantità di codice nei tuoi metodi privati ​​- dovresti comunque farlo comunque - e verifica indirettamente dove puoi tramite il comportamento di altri metodi. Non ti aspetti mai di ottenere una copertura del test al 100% e forse dovrai controllare manualmente alcuni valori tramite il debugger. L'uso di metodi privati ​​per generare eccezioni può essere facilmente testato indirettamente. Potrebbe essere necessario testare le proprietà private manualmente o tramite un altro metodo.
  3. Se il controllo indiretto o manuale non ti soddisfa, aggiungi un evento protetto e accedi tramite un'interfaccia per esporre alcune cose private. Questo "piega" efficacemente le regole dell'incapsulamento, ma evita la necessità di spedire effettivamente il codice che esegue i test. Lo svantaggio è che ciò può comportare un piccolo codice interno aggiuntivo per assicurarsi che l'evento venga generato quando necessario.
  4. Se ritieni che un metodo pubblico non sia abbastanza "sicuro", vedi se ci sono modi in cui puoi implementare una sorta di processo di validazione nei tuoi metodi per limitare il loro utilizzo. È probabile che mentre stai pensando a questo o pensi a un modo migliore per implementare i tuoi metodi, o vedrai un'altra classe che inizia a prendere forma.
  5. Se hai molti metodi privati ​​che fanno "cose" per i tuoi metodi pubblici, potrebbe esserci una nuova classe in attesa di essere estratta. Puoi testarlo direttamente come una classe separata, ma implementarlo come un composto privatamente all'interno della classe che lo utilizza.

Pensa lateralmente. Mantieni le tue classi piccole e i tuoi metodi più piccoli e usa molta composizione. Sembra più lavoro, ma alla fine ti ritroverai con più oggetti individualmente testabili, i tuoi test saranno più semplici, avrai più opzioni per usare semplici mock al posto di oggetti reali, grandi e complessi, si spera bene- codice fattorizzato e debolmente accoppiato e, cosa più importante, ti darai più opzioni. Mantenere le cose piccole tende a farti risparmiare tempo alla fine, perché riduci il numero di cose che devi controllare individualmente su ogni classe e tendi a ridurre naturalmente il codice spaghetti che a volte può accadere quando una classe diventa grande e ha un sacco di comportamento del codice interdipendente internamente.


4

Bene, è possibile per me creare un oggetto che ha solo metodi privati ​​e interagisce con altri oggetti ascoltando i loro eventi. Questo sarebbe molto incapsulato, ma completamente non verificabile.

Come reagisce questo oggetto a quegli eventi? Presumibilmente, deve invocare metodi su altri oggetti. Puoi testarlo verificando se questi metodi vengono chiamati. Fallo chiamare un oggetto finto e poi puoi facilmente affermare che fa quello che ti aspetti.

Il problema è che vogliamo solo testare l'interazione dell'oggetto con altri oggetti. Non ci importa cosa succede dentro un oggetto. Quindi no, non dovresti avere più metodi pubblici di prima.


4

Ho anche lottato con questo stesso problema. Davvero, il modo per aggirare il problema è questo: come ti aspetti che il resto del tuo programma si interfaccia con quella classe? Metti alla prova la tua classe di conseguenza. Questo ti costringerà a progettare la tua classe in base a come il resto del programma si interfaccia con essa e, di fatto, incoraggerà l'incapsulamento e il buon design della tua classe.


3

Invece del modificatore predefinito di uso privato. Quindi puoi testare questi metodi singolarmente, non solo accoppiato con metodi pubblici. Ciò richiede che i test abbiano la stessa struttura del pacchetto del codice principale.


... supponendo che questo sia Java.
Dawood ibn Kareem,

o internalin .net.
CodesInChaos,

2

Alcuni metodi privati ​​di solito non sono un problema. Li testi semplicemente attraverso l'API pubblica come se il codice fosse incorporato nei tuoi metodi pubblici. Un eccesso di metodi privati può essere un segno di scarsa coesione. La tua classe dovrebbe avere una responsabilità coesa e spesso le persone rendono i metodi privati ​​per dare l'apparenza di coesione laddove non esiste davvero.

Ad esempio, potresti avere un gestore eventi che effettua molte chiamate al database in risposta a quegli eventi. Dato che è ovviamente una cattiva pratica istanziare un gestore di eventi per effettuare chiamate al database, la tentazione è di rendere tutti i metodi privati ​​delle chiamate relative al database, quando dovrebbero essere effettivamente estratti in una classe separata.


2

Questo significa che TDD è in contrasto con l'incapsulamento? Qual è il giusto equilibrio? Sono propenso a rendere pubblici la maggior parte o tutti i miei metodi ora.

TDD non è in contrasto con l'incapsulamento. Prendi l'esempio più semplice di un metodo getter o di una proprietà, a seconda della lingua che preferisci. Diciamo che ho un oggetto Cliente e voglio che abbia un campo ID. Il primo test che scriverò è quello che dice qualcosa come "customer_id_initializes_to_zero". Definisco il getter per lanciare un'eccezione non implementata e guardare il test fallire. Quindi, la cosa più semplice che posso fare per fare quel passaggio di test è avere il ritorno a zero zero.

Da lì, vado su altri test, presumibilmente quelli che coinvolgono l'ID cliente in un campo reale e funzionale. Ad un certo punto, probabilmente dovrò creare un campo privato che la classe del cliente utilizza per tenere traccia di ciò che dovrebbe essere restituito dal getter. Come faccio a tenerne traccia esattamente? È un semplice supporto int? Tengo traccia di una stringa e poi la converto in int? Tengo traccia di 20 ints e li faccio in media? Al mondo esterno non importa e ai tuoi test TDD non importa. Questo è un dettaglio incapsulato .

Penso che questo non sia sempre immediatamente evidente quando si avvia TDD - non si stanno testando quali metodi fanno internamente - si stanno testando preoccupazioni meno granulari della classe. Quindi, non stai cercando di testare quel metodo che DoSomethingToFoo()crea un'istanza di una barra, chiama un metodo su di essa, ne aggiunge due a una delle sue proprietà, ecc. Stai testando che dopo aver mutato lo stato del tuo oggetto, alcuni accessor dello stato sono cambiati (o no). Questo è lo schema generale dei tuoi test: "quando faccio X alla mia classe sotto test, posso successivamente osservare Y". Il modo in cui si arriva a Y non è una preoccupazione dei test, e questo è ciò che è incapsulato ed è per questo che TDD non è in contrasto con l'incapsulamento.


2

Evitare di utilizzare? No.
Evitare di iniziare ? Sì.

Ho notato che non hai chiesto se è giusto avere lezioni astratte con TDD; se capisci come emergono le classi astratte durante il TDD, lo stesso principio si applica anche ai metodi privati.

Non puoi testare direttamente metodi in classi astratte proprio come non puoi testare direttamente metodi privati, ma è per questo che non inizi con classi astratte e metodi privati; inizi con classi concrete e API pubbliche e poi rifletti le funzionalità comuni mentre procedi.

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.