Devo evitare i metodi privati se eseguo TDD?

93

Sto solo imparando TDD. Sono a conoscenza del fatto che i metodi privati non sono testabili e non dovrebbero essere preoccupati perché l'API pubblica fornirà informazioni sufficienti per verificare l'integrità di un oggetto.

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

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

Inoltre, è considerato una cattiva pratica aggiungere metodi per il test.

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

    
posta pup 14.02.2012 - 16:58
fonte

12 risposte

43

Preferisci test all'interfaccia tramite test sull'implementazione.

It's my understanding that private methods are untestable

Dipende dal tuo ambiente di sviluppo, vedi sotto.

[private methods] shouldn't be worried about because the public API will provide enough information for verifying an object's integrity.

È vero, TDD si concentra sul test dell'interfaccia.

I metodi privati sono dettagli di implementazione che potrebbero cambiare durante ogni ciclo di fattore di fattore. Dovrebbe essere possibile ri-fattore senza cambiare l'interfaccia o il comportamento black-box . In effetti, questo è parte del vantaggio di TDD, la facilità con cui è possibile generare la sicurezza che cambia all'interno di una classe non influirà sugli utenti di quella classe.

Well, it's possible for me to make an object that has only private methods and interacts with other objects by listening to their events. This would be very encapsulated, but completely untestable.

Anche se la classe non ha metodi pubblici, i suoi gestori di eventi sono la interfaccia pubblica , ed è contro quella interfaccia pubblica che tu può testare.

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

Cerca di utilizzare oggetti fittizi come colla per il tuo sistema di test. Dovrebbe essere possibile creare un semplice oggetto mock che genera un evento e preleva il cambiamento di stato risultante (possibile da un altro oggetto fittizio del ricevitore).

Also, it's considered bad practice to add methods for the sake of testing.

Assolutamente, dovresti essere molto cauto di esporre lo stato interno.

Does this mean TDD is at odds with encapsulation? What is the appropriate balance?

Assolutamente no.

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

La migliore pratica con TDD è identica alla migliore pratica senza TDD, basta scoprire perché prima, perché stai usando l'interfaccia mentre la sviluppi.

I'm inclined to make most or all of my methods public now...

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

Non dovresti aver bisogno di per rendere pubblici tutti i metodi in modo da poterti sviluppare in un modo TDD. Vedi le mie note qui sotto per vedere se i tuoi metodi privati non sono realmente testabili.

Un'occhiata più dettagliata ai test dei metodi privati

Se tu devi assolutamente testare un comportamento privato di una classe, a seconda della lingua / ambiente, potresti avere tre opzioni:

  1. Metti i test nella classe che vuoi testare.
  2. Metti i test in un altro file di classe / sorgente & esporre i metodi privati che desideri testare come metodi pubblici.
  3. Utilizzare un ambiente di test che ti consenta di mantenere separati il codice di test e di produzione, consentendo comunque l'accesso al codice di prova 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)

L'archiviazione dei 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 codice di prova 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 desideri testare come metodi pubblici (non è una buona idea)

Come suggerito, questa pratica è molto scarsa, 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 di Eclipse, 3. può essere ottenuto usando frammenti . Nel mondo C #, potremmo utilizzare classi parziali . Altri linguaggi / ambienti hanno spesso funzionalità simili, devi solo trovarlo.

Assumendo ciecamente 1. o 2. sono le uniche opzioni che potrebbero far sì che il software di produzione sia gonfio di codice di test o di interfacce di classe cattive che lavano il loro lino sporco in pubblico. * 8' )

  • Tutto sommato - è molto meglio non testare però con l'implementazione privata.
risposta data 14.02.2012 - 18:28
fonte
75

Ovviamente puoi avere metodi privati e, naturalmente, puoi testarli.

O c'è un un po ' per ottenere il metodo privato da eseguire, nel qual caso puoi testarlo in questo modo, oppure c'è no per far sì che il privato corri, nel qual caso: perché diavolo stai provando a testarlo, elimina semplicemente quella dannata cosa!

Nel tuo esempio:

Well, it's possible for me to make an object that has only private methods and interacts with other objects by listening to their events. This would be very encapsulated, but completely untestable.

Perché sarebbe impossibile da testare? Se il metodo viene invocato in risposta a un evento, basta fare in modo che il test alimentare l'oggetto un evento appropriato.

Non si tratta di non avere metodi privati, si tratta di non interrompere l'incapsulamento. Puoi avere metodi privati ma devi testarli tramite l'API pubblica. Se l'API pubblica è basata su eventi, quindi utilizza gli eventi.

Per il caso più comune di metodi di helper privati, possono essere testati attraverso i metodi pubblici che li chiamano. In particolare, dal momento che puoi solo scrivere codice per far passare un test in errore e testare le API pubbliche, tutto il nuovo codice che scrivi sarà di solito pubblico. I metodi privati vengono visualizzati come risultato di un metodo di rimozione dei dettagli , quando vengono estratti da un metodo pubblico già esistente. Ma in quel caso, il test originale che verifica il metodo pubblico copre ancora il metodo privato, poiché il metodo pubblico chiama il metodo privato.

Quindi, solitamente i metodi privati appaiono solo quando vengono estratti da metodi pubblici già testati e sono quindi già testati.

    
risposta data 14.02.2012 - 17:19
fonte
25

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

Attraverso i test, verifichiamo che il codice faccia ciò che è previsto che faccia , genera le eccezioni appropriate quando previsto, ecc. Non ci interessa davvero come il codice viene implementato dal sviluppatore. Anche se non ci interessa l'implementazione, ovvero come il codice fa quello che fa, ha senso evitare di testare metodi privati.

Come per testare classi che non hanno metodi pubblici e interagire con il mondo esterno solo attraverso eventi, puoi 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, il test dell'unità invierà l'evento e verificherà che il file di registro sia stato scritto.

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

    
risposta data 14.02.2012 - 17:27
fonte
6

It's my understanding that private methods are untestable

Non sono d'accordo con questa affermazione, o direi che non testerai i metodi privati direttamente . Un metodo pubblico può chiamare diversi metodi privati. Forse l'autore voleva avere metodi "piccoli" ed estraeva 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 si scopre che una delle istruzioni branch (if / switch) in un metodo privato non è mai stata inclusa nei test, si ha un problema. O hai perso un caso e l'implementazione è corretta OPPURE l'implementazione è sbagliata, e quel ramo non avrebbe mai dovuto esistere di fatto.

Ecco perché uso molto Cobertura e NCover per assicurarmi che il mio metodo di prova pubblico riguardi anche i metodi privati. Sentiti libero di scrivere buoni oggetti OO con metodi privati e non lasciare che TDD / Test si metta sulla tua strada in questa materia.

    
risposta data 14.02.2012 - 17:20
fonte
5

Il tuo esempio è ancora perfettamente testabile se usi Dependency Injection per fornire le istanze con cui il tuo CUT interagisce. Quindi puoi usare una simulazione, generare gli eventi di interesse e poi osservare se il CUT prende o meno le azioni corrette sulle sue dipendenze.

D'altra parte, se hai una lingua con il supporto di un buon evento, puoi seguire un percorso leggermente diverso. Non mi piace quando gli oggetti si iscrivono agli eventi stessi, ma hanno la fabbrica che crea l'oggetto per collegare gli eventi ai metodi pubblici dell'oggetto. È più facile da testare e rende esternamente visibile quali tipi di eventi è necessario testare il CUT.

    
risposta data 14.02.2012 - 17:23
fonte
5

Non dovresti aver bisogno di abbandonare l'utilizzo di metodi privati. È perfettamente logico utilizzarli, ma dal punto di vista del test è più difficile testare direttamente senza interrompere l'incapsulamento o aggiungere codice specifico del test alle classi. Il trucco consiste nel ridurre al minimo le cose che sai ti fanno star male perché ti senti come se avessi sporcato il tuo codice.

Queste sono le cose che tengo a mente per cercare di ottenere un equilibrio funzionale.

  1. Riduci al minimo il numero di metodi e proprietà privati che utilizzi. La maggior parte delle cose di cui hai bisogno che la tua classe tendere ha bisogno di essere esposta pubblicamente comunque, quindi pensa se hai davvero bisogno di rendere privato quel metodo intelligente.
  2. Riduci al minimo l'amout del codice nei tuoi metodi privati - dovresti farlo in ogni caso - e verifica indirettamente dove puoi tramite il comportamento di altri metodi. Non ti aspetti mai di ottenere una copertura di prova del 100% e forse dovrai controllare alcuni valori tramite il debugger. L'utilizzo di metodi privati per lanciare 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 si adatta bene a te, aggiungi un evento protetto e accedi tramite un'interfaccia per esporre alcune delle cose private. Questo effettivamente "si piega" alle regole dell'incapsulamento, ma evita la necessità di spedire effettivamente il codice che esegue i test. Lo svantaggio è che questo può comportare un piccolo codice interno aggiuntivo per assicurarsi che l'evento venga attivato quando necessario.
  4. Se ritieni che un metodo pubblico non sia abbastanza "sicuro", verifica se esistono dei modi per implementare una sorta di processo di convalida nei tuoi metodi per limitare il modo in cui vengono utilizzati. Le probabilità sono 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 composito in privato 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ù elementi testabili individualmente, i tuoi test saranno più semplici, avrai più opzioni per usare semplici mock al posto di oggetti reali, grandi e complessi, speriamo che codice fattorizzato e liberamente accoppiato, e soprattutto ti darai più opzioni. Mantenere le cose piccole tende a 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ò capitare quando una classe diventa grande e ha un sacco di comportamento di codice interdipendente internamente.

    
risposta data 14.02.2012 - 23:16
fonte
3

Well, it's possible for me to make an object that has only private methods and interacts with other objects by listening to their events. This would be very encapsulated, but completely untestable.

In che modo questo oggetto reagisce a quegli eventi? Presumibilmente, deve invocare metodi su altri oggetti. Puoi testarlo controllando se questi metodi vengono richiamati. Chiamare un oggetto fittizio e quindi puoi facilmente affermare che fa ciò che ti aspetti.

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

    
risposta data 14.02.2012 - 17:21
fonte
3

Ho lottato anche con questo stesso problema. In realtà, il modo per aggirarlo è 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 esso e, di fatto, incoraggerà l'incapsulamento e il buon design della tua classe.

    
risposta data 14.02.2012 - 19:20
fonte
3

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

    
risposta data 14.02.2012 - 20:32
fonte
2

Di solito alcuni metodi privati non sono un problema. Basta testarli tramite l'API pubblica come se il codice fosse inserito nei tuoi metodi pubblici. Un eccesso di metodi privati può essere un segno di scarsa coesione. La tua classe dovrebbe avere una responsabilità coesiva, e spesso le persone rendono i metodi privati per dare l'impressione di coesione dove nessuno esiste veramente.

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

    
risposta data 14.02.2012 - 17:32
fonte
1

Does this mean TDD is at odds with encapsulation? What is the appropriate balance? I'm inclined to make most or all of my methods public now.

Il TDD non è in disaccordo con l'incapsulamento. Prendi l'esempio più semplice di metodo o proprietà getter, a seconda della tua lingua preferita. Diciamo che ho un oggetto Cliente e voglio che abbia un campo ID. Il primo test che sto per scrivere è uno 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 test è avere il getter return zero.

Da lì, vado su altri test, presumibilmente quelli che coinvolgono l'ID cliente come un campo reale e funzionale. A un certo punto, probabilmente dovrò creare un campo privato che la classe cliente usi per tenere traccia di ciò che dovrebbe essere restituito dal getter. Esattamente come tengo traccia di questo? È un semplice supporto int? Ti tengo traccia di una stringa e poi la converto in int? Tengo traccia di 20 pollici e li media? Al mondo esterno non importa - e ai tuoi test TDD non interessa. Questo è un dettaglio incapsulato .

Penso che questo non sia sempre immediatamente ovvio all'avvio di TDD - non stai testando quali metodi internamente - stai testando le preoccupazioni meno granulari della classe. Quindi, non stai cercando di testare quel metodo DoSomethingToFoo() istanzia una barra, chiama un metodo su di esso, aggiunge due a una delle sue proprietà, ecc. Stai testando che dopo aver mutato lo stato del tuo oggetto, alcuni stati accessor è cambiato (o meno). Questo è lo schema generale dei tuoi test: "quando eseguo X nella mia classe sotto esame, posso successivamente osservare Y". Il modo in cui arriva a Y non è una delle preoccupazioni dei test, e questo è ciò che è incapsulato e questo è il motivo per cui TDD non è in disaccordo con l'incapsulamento.

    
risposta data 14.02.2012 - 17:54
fonte
1

Non usare? No.
Evita iniziare con ? Sì.

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

Non è possibile testare direttamente i metodi in classi astratte, proprio come non è possibile testare direttamente i metodi privati, ma è per questo che non si inizia con classi astratte e metodi privati; si inizia con le classi concrete e le API pubbliche, quindi si ridefiniscono le funzionalità comuni man mano che si procede.

    
risposta data 15.02.2012 - 00:44
fonte

Leggi altre domande sui tag