Rendere pubblico un metodo privato per testarlo ... buona idea?


301

Nota del moderatore: ci sono già 39 risposte pubblicate qui (alcune sono state eliminate). Prima di pubblicare la risposta, considera se puoi aggiungere qualcosa di significativo alla discussione. Molto probabilmente stai solo ripetendo ciò che qualcun altro ha già detto.


Occasionalmente mi ritrovo a dover fare un metodo privato in una classe pubblica solo per scrivere alcuni test unitari.

Di solito ciò è dovuto al fatto che il metodo contiene una logica condivisa tra altri metodi della classe ed è più ordinato testare la logica da solo, oppure un altro motivo potrebbe essere possibile se voglio testare la logica utilizzata nei thread sincroni senza doversi preoccupare dei problemi di threading .

Altre persone si trovano a farlo, perché non mi piace davvero farlo ?? Personalmente penso che i bonus superino i problemi di rendere pubblico un metodo che in realtà non fornisce alcun servizio al di fuori della classe ...

AGGIORNARE

Grazie per le risposte a tutti, sembra aver suscitato l'interesse delle persone. Penso che il consenso generale sia che i test dovrebbero avvenire tramite l'API pubblica poiché questo è l'unico modo in cui una classe verrà mai utilizzata e sono d'accordo con questo. La coppia di casi che ho menzionato in precedenza, in cui lo farei sopra, erano casi non comuni e ho pensato che ne valessero la pena.

Riesco comunque a vedere tutti quanti indicano che non dovrebbe mai accadere davvero. E a pensarci un po 'di più, penso che cambiare il tuo codice per adattarlo ai test sia una cattiva idea - dopo tutto suppongo che il test sia uno strumento di supporto in un certo modo e cambiare un sistema per "supportare uno strumento di supporto", se vuoi, è palese cattiva pratica.


2
"La modifica di un sistema per" supportare uno strumento di supporto ", se lo desideri, è palese cattiva pratica". Beh, sì, in un certo senso. Ma OTOH se il sistema non funziona bene con strumenti consolidati, forse qualcosa è sbagliato con il sistema.
Thilo,


1
Non è una risposta, ma puoi sempre accedervi riflettendo.
Zano,

33
No, non dovresti esporli. Invece, dovresti testare che la tua classe si comporti come dovrebbe, tramite la sua API pubblica. E se esporre gli interni è davvero l'unica opzione (di cui dubito), allora dovresti almeno proteggere il pacchetto di accessori, in modo che solo le classi nello stesso pacchetto (come dovrebbe essere il tuo test) possano accedervi.
JB Nizet,

4
Sì. È perfettamente accettabile fornire un metodo pacchetto-visibilità privata (impostazione predefinita) per renderlo accessibile dai test unitari. Le biblioteche come Guava forniscono anche @VisibileForTestingun'annotazione per creare tali metodi - ti consiglio di farlo in modo che il motivo del mancato metodo sia privatedocumentato correttamente.
Boris the Spider,

Risposte:


186

Nota:
questa risposta è stata originariamente inviata per la domanda Il solo test unitario è mai un buon motivo per esporre variabili di istanza private tramite getter? che è stato unito a questo, quindi potrebbe essere un po 'specifico per il caso usato qui presentato.

Come affermazione generale, di solito sono tutto per il refactoring del codice di "produzione" per rendere più semplice il test. Tuttavia, non penso che sarebbe una buona chiamata qui. Un buon test unitario (di solito) non dovrebbe interessare i dettagli di implementazione della classe, ma solo il suo comportamento visibile. Invece di esporre gli stack interni al test, è possibile verificare che la classe restituisca le pagine nell'ordine previsto dopo aver chiamato first()o last().

Ad esempio, considera questo pseudo-codice:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}

8
Sono completamente d'accordo con la tua risposta. Molti sviluppatori sarebbero tentati di utilizzare il modificatore di accesso predefinito e giustificare che buoni framework open source utilizzino questa strategia, quindi deve essere corretta. Solo perché puoi non significa che dovresti. +1
CKing

6
Sebbene altri abbiano fornito utili spunti, devo dire che questa è la soluzione che trovo più adatta a questo problema. Mantiene i test completamente indipendenti dal modo in cui il comportamento previsto viene raggiunto internamente. +10!
Gitahi Ng'ang'a,

A proposito, va bene che nell'esempio fornito da @Mureinik qui, i metodi di test finiscono per coprire più del metodo reale sotto test? Ad esempio, testFirst () chiama next (), che non è proprio il metodo sotto test qui, first () è. Non sarebbe più pulito e chiaro avere 1 solo metodo di prova che copra tutti e 4 i metodi di navigazione?
Gitahi Ng'ang'a,

1
Sebbene questo sia l'ideale, ci sono volte in cui è necessario verificare che un componente abbia fatto qualcosa nel modo giusto non solo per avere successo. Questo può essere più difficile per il test della scatola nera. A volte devi testare un componente in una casella bianca.
Peter Lawrey,

1
Creare getter per il solo scopo di test unitari? Questo va decisamente contro il concetto di OOP / Object Thinking in quanto l'interfaccia dovrebbe esporre il comportamento, non i dati.
IntelliData,

151

Personalmente, preferirei i test unitari usando l'API pubblica e di certo non renderei mai pubblico il metodo privato solo per rendere più semplice il test.

Se vuoi davvero testare il metodo privato in isolamento, in Java puoi usare Easymock / Powermock per permetterti di farlo.

Devi essere pragmatico al riguardo e dovresti anche essere consapevole dei motivi per cui le cose sono difficili da testare.

' Ascolta i test ' - se è difficile testarlo, ti sta dicendo qualcosa sul tuo design? Potresti riflettere su dove un test per questo metodo sarebbe banale e facilmente coperto dai test attraverso l'API pubblico?

Ecco cosa dice Michael Feathers in " Lavorare in modo efficace con il codice legacy "

"Molte persone passano molto tempo a cercare di capire come aggirare questo problema ... la vera risposta è che se hai la voglia di testare un metodo privato, il metodo non dovrebbe essere privato; se rendilo pubblico ti dà fastidio, è probabile, è perché fa parte di una responsabilità separata; dovrebbe essere in un'altra classe. " [ Lavorare efficacemente con Legacy Code (2005) di M. Feathers]


5
+1 per Easymock / Powermock e non rendere mai pubblico un metodo privato
Leonard Brünings

51
+1 per "Ascolta i test". Se senti la necessità di testare un metodo privato, è probabile che la logica in quel metodo sia così complessa che dovrebbe essere in una classe helper separata. Quindi puoi testare la classe helper.
Phil,

2
'Ascolta i test' sembra essere inattivo. Ecco un link nella cache: webcache.googleusercontent.com/…
Jess Telford,

1
Sono d'accordo con @Phil (in particolare il riferimento del libro), ma spesso mi trovo in una situazione di pollo / uovo con codice legacy. So che questo metodo appartiene a un'altra classe, tuttavia non sono in grado di eseguire il refactoring al 100% senza test in primo luogo.
Kevin,

Concordo dottore. Se è necessario testare un metodo privato, probabilmente dovrebbe trovarsi in un'altra classe. Si può notare che l'altra classe / classe di supporto è probabile che il metodo privato diventi pubblico / protetto.
rupweb,

67

Come altri hanno già detto, è in qualche modo sospetto che si tratti di test unitari su metodi privati; unit test dell'interfaccia pubblica, non dei dettagli dell'implementazione privata.

Detto questo, la tecnica che uso quando voglio testare qualcosa di privato in C # è di declassare la protezione dell'accessibilità da privata a interna e quindi contrassegnare l'assemblaggio di test unità come un assieme amico usando InternalsVisibleTo . L'assemblea di collaudo dell'unità sarà quindi autorizzata a trattare gli interni come pubblici, ma non devi preoccuparti di aggiungere accidentalmente alla tua area pubblica.


Uso anche questa tecnica, ma come ultima risorsa per il codice legacy di solito. Se devi testare il codice privato, hai una classe mancante / la classe sotto test sta facendo troppo. Estrai detto codice in una nuova classe che puoi provare isolatamente. Trucchi come questo dovrebbero essere usati raramente.
Finglas,

Ma l'estrazione di una classe e il solo test significa che i tuoi test perdono la consapevolezza che la classe originale sta effettivamente utilizzando il codice estratto. Come mi consiglia di colmare questa lacuna nei test? E se la risposta è semplicemente testare l'interfaccia pubblica della classe originale in un modo che fa sì che venga utilizzata la logica estratta, allora perché preoccuparsi di estrarla in primo luogo? (Non ho intenzione di sembrare conflittuale qui, a proposito - mi sono sempre chiesto come le persone bilancino tali compromessi.)
Mal Ross,

@mal Avrei un test di collaborazione. In altre parole, un mock per verificare che la nuova classe fosse invocata correttamente. Vedi la mia risposta qui sotto.
Finglas,

Si potrebbe scrivere una classe di test che eredita dalla classe con la logica interna che si desidera testare e fornire un metodo pubblico da utilizzare per testare la logica interna?
Sholsinger,

1
@sholsinger: in C # una classe pubblica non può ereditare da una classe interna.
Eric Lippert,

45

Molte risposte suggeriscono solo di testare l'interfaccia pubblica, ma IMHO non è realistico: se un metodo fa qualcosa che richiede 5 passaggi, ti consigliamo di provare questi cinque passaggi separatamente, non tutti insieme. Ciò richiede il test di tutti e cinque i metodi, che (diversamente da quelli per i test) potrebbero altrimenti essere private.

Il solito modo di testare metodi "privati" è quello di dare a ogni classe la propria interfaccia e creare i metodi "privati" public, ma non includerli nell'interfaccia. In questo modo, possono ancora essere testati, ma non gonfiano l'interfaccia.

Sì, questo si tradurrà in file e classe gonfia.

Sì, questo rende ridondanti gli specificatori publice private.

Sì, questo è un dolore nel culo.

Questo è, purtroppo, uno dei tanti sacrifici che facciamo per rendere testabile il codice . Forse un linguaggio futuro (o anche una versione futura di C # / Java) avrà funzionalità per rendere più conveniente la testabilità di classi e moduli; ma nel frattempo, dobbiamo saltare attraverso questi cerchi.


Alcuni sostengono che ognuna di queste fasi dovrebbe essere la propria classe , ma non sono d'accordo - se condividono tutti lo stato, non c'è motivo di creare cinque classi separate in cui farebbero cinque metodi. Ancora peggio, questo si traduce in file e classe gonfia. Inoltre , infetta l'API pubblica del tuo modulo - tutte quelle classi devono necessariamente essere publicse vuoi testarle da un altro modulo (o quello, o includere il codice di prova nello stesso modulo, il che significa spedire il codice di prova con il tuo prodotto) .


2
bella risposta per la prima metà
cmcginty

7
+1: la risposta migliore per me. Se un produttore di auto non testa una parte della sua auto per lo stesso motivo, non sono sicuro che qualcuno lo comprerebbe. Parte importante del codice dovrebbe essere testata anche se dovessimo cambiarla in pubblico.
Tae-Sung Shin,

1
Ottima risposta Hai il mio caloroso voto. Ho aggiunto una risposta Potrebbe interessarti.
davidxxx,

29

Un unit test dovrebbe testare il contratto pubblico, l'unico modo in cui una classe potrebbe essere utilizzata in altre parti del codice. Un metodo privato è i dettagli dell'implementazione, non dovresti testarlo, per quanto l'API pubblica funzioni correttamente, l'implementazione non ha importanza e potrebbe essere modificata senza modifiche nei casi di test.



D'altronde, la rara situazione in cui ho mai deciso di usare i privati ​​in un caso di test è stata quando stavo verificando la correttezza di un oggetto - se ha chiuso tutti i gestori JNI. Ovviamente, non è esposto come API pubblica, tuttavia, se non accade, si verifica una perdita di memoria. Ma divertente, in seguito me ne sono liberato anche dopo aver introdotto un rilevatore di perdite di memoria come parte dell'API pubblica.
kan

11
Questa è una logica imperfetta. Il punto del test unitario è quello di rilevare gli errori prima o poi. Non testando metodi privati, si stanno lasciando vuoti nei test che potrebbero non essere scoperti fino a molto tempo dopo. Nel caso in cui il metodo privato sia complesso e non facilmente esercitabile da un'interfaccia pubblica, dovrebbe essere testato in unità.
cmcginty

6
@Casey: se il metodo privato non è esercitato da un'interfaccia pubblica, perché esiste?
Thilo,

2
@DaSilva_Ireland: in futuro sarà difficile mantenere i casi di test. L'unico caso d'uso di classe reale è tramite metodo pubblico. Vale a dire tutti gli altri codici potrebbero usare la classe solo tramite API pubblica. Quindi, se vuoi cambiare l'implementazione e fallire il caso di test del metodo privato, ciò non significa che stai facendo qualcosa di sbagliato, inoltre, se stai testando solo il privato, ma non testando completamente il pubblico, non coprirà alcuni potenziali bug . Idealmente un caso di test dovrebbe coprire tutti i possibili casi e solo i casi di utilizzo reale della classe.
kan

22

IMO, dovresti scrivere i tuoi test senza fare profonde ipotesi su come la tua classe è stata implementata all'interno. Probabilmente vorrai riformattarlo in un secondo momento utilizzando un altro modello interno, ma facendo comunque le stesse garanzie che la precedente implementazione offre.

Tenendo presente questo, ti suggerisco di concentrarti sul test che il tuo contratto è ancora valido, indipendentemente dall'implementazione interna che la tua classe attualmente ha. Test basati sulle proprietà delle tue API pubbliche.


6
Sono d'accordo che i test unitari che non devono essere riscritti dopo un refactoring del codice siano migliori. E non solo per il tempo che passeresti a riscrivere i test unitari. Un motivo molto più importante è che ritengo che lo scopo più importante dei test unitari sia che il codice si comporti correttamente dopo un refactoring. Se devi riscrivere tutti i test unitari dopo un refactoring, perdi quel vantaggio dei test unitari.
Kasperd,

20

Che ne dici di renderlo pacchetto privato? Quindi il tuo codice di test può vederlo (e anche altre classi nel tuo pacchetto), ma è ancora nascosto ai tuoi utenti.

Ma davvero, non dovresti testare metodi privati. Questi sono dettagli di implementazione e non fanno parte del contratto. Tutto ciò che fanno dovrebbe essere coperto chiamando i metodi pubblici (se contengono codice che non è esercitato dai metodi pubblici, allora dovrebbe andare). Se il codice privato è troppo complesso, probabilmente la classe sta facendo troppe cose e ha bisogno di refactoring.

Rendere pubblico un metodo è un grande impegno. Una volta che lo fai, le persone saranno in grado di usarlo e non puoi più cambiarlo.


+1 se hai bisogno di testare i tuoi privati ​​probabilmente stai perdendo una classe
jk.

+1 per la classe mancante. Sono contento di vedere gli altri non saltare subito sul carrozzone "mark it internal".
Finglas,

Ragazzo, ho dovuto fare molta strada per trovare la risposta privata del pacchetto.
Bill K,

17

Aggiornamento : ho aggiunto una risposta più estesa e più completa a questa domanda in numerosi altri luoghi. Questo è disponibile sul mio blog .

Se mai avessi bisogno di rendere pubblico qualcosa per testarlo, questo di solito suggerisce che il sistema sotto test non sta seguendo il principio di responsabilità singola . Quindi c'è una classe mancante che dovrebbe essere introdotta. Dopo aver estratto il codice in una nuova classe, renderlo pubblico. Ora puoi testare facilmente e stai seguendo SRP. L'altra tua classe deve semplicemente invocare questa nuova classe tramite composizione.

Rendere pubblici i metodi / usare trucchi di lingua come contrassegnare il codice come visibile per testare gli assiemi dovrebbe essere sempre l'ultima risorsa.

Per esempio:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Rifattorizzare ciò introducendo un oggetto validatore.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Ora tutto ciò che dobbiamo fare è verificare che il validatore sia invocato correttamente. L'effettivo processo di validazione (la logica precedentemente privata) può essere testato in puro isolamento. Non sarà necessario impostare complessi test per garantire il superamento di questa convalida.


1
Personalmente penso che SRP possa essere portato troppo lontano, ad esempio se scrivo un servizio di account che si occupa di aggiornare / creare / eliminare account in un'applicazione, mi stai dicendo che dovresti creare una classe separata per ogni operazione? E per quanto riguarda la validazione, ad esempio, vale davvero la pena estrarre la logica di validazione in un'altra classe quando non verrà mai usata da nessun'altra parte? imo che rende il codice meno leggibile, meno conciso e comprime la tua applicazione con classi superflue!
jcvandan,

1
Tutto dipende dallo scenario. La definizione di "fare una cosa" di una persona può essere diversa dagli altri. In questo caso per te, chiaramente il codice privato che desideri testare è complesso / un dolore da impostare. Il fatto stesso che desideri testarlo dimostra che sta facendo troppo. Quindi sì, avere un nuovo oggetto sarà l'ideale.
Finglas,

1
Quanto al rendere il tuo codice meno leggibile, non sono d'accordo. Avere una classe per la validazione è più facile da leggere che avere la validazione incorporata nell'altro oggetto. Hai ancora la stessa quantità di codice, non è nidificato in una classe di grandi dimensioni. Preferirei avere diverse classi che fanno una cosa molto bene, piuttosto che una grande classe.
Finglas,

4
@dormisher, per quanto riguarda il servizio del tuo account, considera l'SRP come un "unico motivo per cambiare". Se le tue operazioni CRUD cambieranno naturalmente insieme, si adatterebbero all'SRP. Normalmente, li cambieresti perché lo schema del database potrebbe cambiare, il che influenzerebbe naturalmente l'inserimento e l'aggiornamento (anche se forse non sempre eliminano).
Anthony Pegram,

@finglas cosa ne pensi: stackoverflow.com/questions/29354729/… . Non ho voglia di testare il metodo privato, quindi forse non dovrei separarlo in una classe, ma viene utilizzato in altri 2 metodi pubblici, quindi finirei per testare due volte la stessa logica.
Rodrigo Ruiz,

13

Alcune grandi risposte. Una cosa che non ho visto menzionato è che con lo sviluppo test-driven (TDD), i metodi privati ​​vengono creati durante la fase di refactoring (vedi Metodo di estrazione per un esempio di un modello di refactoring) e quindi dovrebbero già avere la copertura di test necessaria . Se fatto correttamente (e ovviamente, otterrai una serie mista di opinioni quando si tratta di correttezza), non dovresti preoccuparti di dover rendere pubblico un metodo privato solo per poterlo testare.


11

Perché non suddividere l'algoritmo di gestione dello stack in una classe di utilità? La classe di utilità può gestire gli stack e fornire accessi pubblici. I test unitari possono essere focalizzati sui dettagli di implementazione. Test approfonditi per le classi algoritmicamente difficili sono molto utili per raggrinzire i casi limite e garantire la copertura.

Quindi la classe corrente può delegare in modo chiaro alla classe di utilità senza esporre alcun dettaglio di implementazione. I suoi test riguarderanno i requisiti di impaginazione come altri hanno raccomandato.


10

In java, c'è anche la possibilità di renderlo pacchetto privato (cioè lasciando fuori il modificatore di visibilità). Se i test unitari si trovano nello stesso pacchetto della classe testata, dovrebbero essere in grado di vedere questi metodi ed è un po 'più sicuro rispetto a rendere il metodo completamente pubblico.


10

I metodi privati ​​sono generalmente usati come metodi "helper". Pertanto restituiscono solo valori di base e non operano mai su istanze specifiche di oggetti.

Hai un paio di opzioni se vuoi testarle.

  • Usa la riflessione
  • Concedi l'accesso al pacchetto metodi

In alternativa, è possibile creare una nuova classe con il metodo helper come metodo pubblico se è un candidato abbastanza valido per una nuova classe.

C'è un ottimo articolo qui su questo.


1
Commento equo. Era solo un'opzione, forse una cattiva.
adamjmarkham,

@Finglas Perché testare il codice privato usando reflection è una cattiva idea?
Anshul,

I metodi privati ​​@Anshul sono dettagli di implementazione e non comportamento. In altre parole, cambiano. I nomi cambiano. I parametri cambiano. Il contenuto cambia. Ciò significa che questi test si interromperanno continuamente. D'altra parte, i metodi pubblici sono molto più stabili in termini di API, quindi sono più resistenti. Fornendo i test sui metodi pubblici per verificare il comportamento e non i dettagli di implementazione, dovresti essere bravo.
Finglas,

1
@Finglas Perché non vorresti testare i dettagli di implementazione? È il codice che deve funzionare e, se cambia più spesso, ti aspetti che si interrompa più spesso, che è un motivo in più per testarlo più dell'API pubblica. Dopo che la tua base di codice è stabile, supponiamo che in futuro arrivi qualcuno e cambi un metodo privato che interrompe il funzionamento interno della tua classe. Un test che mette alla prova gli interni della tua classe direbbe immediatamente che hanno rotto qualcosa. Sì, renderebbe più difficile il mantenimento dei test, ma è qualcosa che ti aspetti dal test di copertura completo.
Anshul,

1
@Finglas Testare i dettagli dell'implementazione non è sbagliato. Questa è la tua posizione al riguardo, ma questo è un argomento ampiamente dibattuto. Testare gli interni offre alcuni vantaggi, anche se potrebbero non essere necessari per la copertura completa. I tuoi test sono più focalizzati perché stai testando direttamente i metodi privati ​​invece di passare attraverso l'API pubblica, il che potrebbe confondere il tuo test. Il test negativo è più semplice perché è possibile invalidare manualmente i membri privati ​​e assicurarsi che l'API pubblica restituisca errori appropriati in risposta allo stato interno incoerente. Ci sono altri esempi.
Anshul,

9

Se stai usando C # puoi rendere il metodo interno. In questo modo non inquini le API pubbliche.

Quindi aggiungi l'attributo alla dll

[assembly: InternalsVisibleTo ("MyTestAssembly")]

Ora tutti i metodi sono visibili nel progetto MyTestAssembly. Forse non è perfetto, ma meglio quindi rendere pubblico il metodo privato solo per testarlo.


7

Usa la riflessione per accedere alle variabili private, se necessario.

Ma davvero, non ti interessa lo stato interno della classe, vuoi solo testare che i metodi pubblici restituiscono ciò che ti aspetti nelle situazioni che puoi prevedere.


Cosa faresti con un metodo vuoto?
IntelliData,

@IntelliData Usa la riflessione per accedere alle variabili private, se necessario.
Daniel Alexiuc,

6

Direi che è una cattiva idea perché non sono sicuro che si ottengano benefici e potenziali problemi lungo la linea. Se stai modificando il contratto di una chiamata, solo per testare un metodo privato, non stai testando la classe su come sarebbe utilizzata, ma creando uno scenario artificiale che non hai mai pensato di accadere.

Inoltre, dichiarando il metodo come pubblico, cosa c'è da dire che tra sei mesi (dopo aver dimenticato che l'unica ragione per rendere pubblico un metodo è per il test) che tu (o se hai consegnato il progetto) qualcuno ha vinto in modo completamente diverso non usarlo, portando a conseguenze potenzialmente indesiderate e / o un incubo di manutenzione.


1
che dire se la classe è l'implementazione di un contratto di servizio e viene sempre usata solo tramite la sua interfaccia - in questo modo anche se il metodo privato è stato reso pubblico, non verrà chiamato comunque in quanto non visibile
jcvandan

Vorrei comunque testare la classe utilizzando l'API pubblica (interfaccia), perché altrimenti se si refactoring la classe per suddividere il metodo interno, allora dovresti cambiare i test, il che significa che dovresti spendere un po ' sforzo per garantire che i nuovi test funzionino allo stesso modo dei vecchi test.
beny23,

Inoltre, se l'unico modo per garantire che la copertura del test includa tutti i casi limite all'interno di un metodo privato è chiamarlo direttamente, potrebbe essere indicativo che la complessità della classe meriti il ​​refactoring per semplificarlo.
beny23,

@dormisher: se la classe viene sempre utilizzata solo tramite l'interfaccia, è sufficiente verificare che i metodi dell'interfaccia pubblica si comportino correttamente. Se i metodi pubblici fanno quello che dovrebbero fare, perché ti preoccupi di quelli privati?
Andrzej Doyle,

@Andrzej - Suppongo che non dovrei davvero ma ci sono situazioni in cui credo che potrebbe essere valido, ad esempio un metodo privato che convalida lo stato del modello, potrebbe valere la pena testare un metodo come questo - ma un metodo come questo dovrebbe essere verificabile tramite l'interfaccia pubblica comunque
jcvandan,

6

in termini di unit test, non dovresti assolutamente aggiungere altri metodi; Credo che faresti meglio a fare un caso di prova proprio sul tuo first()metodo, che sarebbe chiamato prima di ogni test; quindi è possibile chiamare più volte il - next(), previous()e last()per vedere se i risultati corrispondono vostra aspettativa. Immagino che se non aggiungi altri metodi alla tua classe (solo a scopo di test), ti atterresti al principio del "black box";



5

Nel tuo aggiornamento dici che è bene testare semplicemente usando l'API pubblica. In realtà ci sono due scuole qui.

  1. Test su scatola nera

    La scuola della scatola nera dice che la classe dovrebbe essere considerata come una scatola nera che nessuno può vedere l'implementazione al suo interno. L'unico modo per testarlo è tramite l'API pubblica, proprio come verrà utilizzata dall'utente della classe.

  2. test su scatola bianca.

    La scuola della scatola bianca pensa naturalmente di usare le conoscenze sull'implementazione della classe e quindi testare la classe per sapere che funziona come dovrebbe.

Non posso davvero prendere parte alla discussione. Ho solo pensato che sarebbe interessante sapere che ci sono due modi distinti per testare una classe (o una libreria o altro).


4

In realtà ci sono situazioni in cui dovresti farlo (ad esempio quando stai implementando alcuni algoritmi complessi). Fallo solo pacchetto-privato e questo sarà sufficiente. Ma nella maggior parte dei casi probabilmente hai classi troppo complesse che richiedono il factoring della logica in altre classi.


4

I metodi privati ​​che vuoi testare isolatamente indicano che c'è un altro "concetto" sepolto nella tua classe. Estrai quel "concetto" nella sua stessa classe e testalo come "unità" separata.

Dai un'occhiata a questo video per una visione davvero interessante dell'argomento.


4

Non dovresti mai e poi mai lasciare che i tuoi test dettino il tuo codice. Non sto parlando di TDD o di altri DD, intendo esattamente quello che stai chiedendo. La tua app necessita che questi metodi siano pubblici? Se lo fa, quindi testarli. In caso contrario, non renderli pubblici solo per i test. Lo stesso vale per le variabili e altri. Lascia che le esigenze della tua applicazione dettino il codice e permetti ai tuoi test di verificare che la necessità sia soddisfatta. (Anche in questo caso non intendo test prima o no intendo cambiare una struttura di classi per raggiungere un obiettivo di test).

Invece dovresti "testare più in alto". Testare il metodo che chiama il metodo privato. Ma i tuoi test dovrebbero testare le esigenze delle tue applicazioni e non le tue "decisioni di implementazione".

Ad esempio (bod pseudo code qui);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

Non c'è motivo di provare "aggiungi", puoi invece provare "libri".

Non lasciare mai che i tuoi test prendano decisioni di progettazione del codice per te. Verifica che ottieni il risultato atteso, non come ottieni quel risultato.


1
"Non lasciare mai che i tuoi test prendano decisioni di progettazione del codice per te" - Non sono d'accordo. Essere difficili da testare è un odore di codice. Se non riesci a provarlo, come fai a sapere che non è rotto?
Alfred Armstrong,

Per chiarimenti, sono d'accordo che non dovresti modificare il design dell'API esterna di una libreria, ad esempio. Ma potresti aver bisogno di refactoring per renderlo più testabile.
Alfred Armstrong,

Ho scoperto che se hai bisogno di refactoring per testare, questo non è l'odore del codice di cui dovresti preoccuparti. Hai qualcos'altro che non va. Ovviamente si tratta di un'esperienza personale, e probabilmente entrambi potremmo discutere su dati solidi in entrambi i modi. Non ho mai avuto "prove difficili" che non erano "design scadente" in primo luogo.
Coteyr,

Questa soluzione è utile per un metodo come i libri che restituiscono un valore: puoi verificare il tipo restituito in un'asserzione ; ma come faresti per verificare un metodo vuoto ?
IntelliData,

metodi nulli fanno qualcosa o non hanno bisogno di esistere. Controlla quello che fanno,
coteyr il

2

Tendo a concordare sul fatto che i bonus di averlo testato unità superano i problemi di aumentare la visibilità di alcuni dei membri. Un leggero miglioramento è renderlo protetto e virtuale, quindi sovrascriverlo in una classe di test per esporlo.

In alternativa, se si tratta di funzionalità che si desidera testare separatamente, non suggerisce un oggetto mancante dal progetto? Forse potresti metterlo in una classe testabile separata ... quindi la tua classe esistente si limita a delegare un'istanza di questa nuova classe.


2

In genere, mantengo le classi di test nello stesso progetto / assieme delle classi in prova.
In questo modo ho solo bisogno di internalvisibilità per rendere testabili funzioni / classi.

Questo in qualche modo complica il processo di costruzione, che deve filtrare le classi di test. Ci riesco nominando tutte le mie classi di test TestedClassTeste usando una regex per filtrare quelle classi.

Questo ovviamente si applica solo alla parte C # / .NET della tua domanda


2

Spesso vorrei aggiungere un metodo chiamato qualcosa come validate, verify, check, ecc, a una classe in modo che possa essere chiamato a verificare lo stato interno di un oggetto.

A volte questo metodo è racchiuso in un blocco ifdef (scrivo principalmente in C ++) in modo che non sia compilato per il rilascio. Ma è spesso utile in versione fornire metodi di validazione che guidano gli alberi degli oggetti del programma controllando le cose.


2

Guava ha un'annotazione @VisibleForTesting per la marcatura di metodi che hanno un ambito ampliato (pacchetto o pubblico) che altrimenti farebbero. Uso un'annotazione @Private per la stessa cosa.

Mentre l'API pubblica deve essere testata, a volte è conveniente e ragionevole arrivare a cose che normalmente non sarebbero pubbliche.

Quando:

  • una classe è resa significativamente meno leggibile, in toto, suddividendola in più classi,
  • solo per renderlo più testabile,
  • e fornire un accesso di prova alle viscere farebbe il trucco

sembra che la religione stia battendo l'ingegneria.


2

Di solito lascio questi metodi protectede inserisco il test unitario nello stesso pacchetto (ma in un altro progetto o cartella di origine), dove possono accedere a tutti i metodi protetti perché il caricatore di classe li posizionerà nello stesso spazio dei nomi.


2

No, perché ci sono modi migliori per scuoiare quel gatto.

Alcuni cablaggi di unit test si basano su macro nella definizione della classe che si espandono automagicamente per creare hook quando sono incorporati in modalità test. Stile molto C, ma funziona.

Un linguaggio OO più semplice è rendere tutto ciò che si desidera testare "protetto" anziché "privato". Il cablaggio di test eredita dalla classe sotto test e può quindi accedere a tutti i membri protetti.

Oppure scegli l'opzione "amico". Personalmente questa è la caratteristica di C ++ che mi piace meno perché infrange le regole di incapsulamento, ma sembra essere necessario per come C ++ implementa alcune caratteristiche, quindi hey ho.

Ad ogni modo, se si esegue il test unitario, è altrettanto probabile che sia necessario immettere valori in tali membri. Gli SMS nella casella bianca sono perfettamente validi. E che in realtà sarebbe rompere il vostro incapsulamento.


2

In .Net esiste una classe speciale chiamata PrivateObjectdegnata specificatamente per permetterti di accedere a metodi privati ​​di una classe.

Scopri di più su MSDN o qui su Stack Overflow

(Mi chiedo che nessuno lo abbia menzionato finora.)

Ci sono situazioni in cui ciò non è abbastanza, nel qual caso dovrai usare la riflessione.

Aderirei comunque alla raccomandazione generale di non testare metodi privati, tuttavia come al solito ci sono sempre delle eccezioni.


1

Come ampiamente notato dai commenti degli altri, i test unitari dovrebbero concentrarsi sull'API pubblica. Tuttavia, a parte i pro / contro e la giustificazione, puoi chiamare metodi privati ​​in un test unitario usando reflection. Ovviamente dovresti assicurarti che la tua sicurezza JRE lo consenta. La chiamata di metodi privati ​​è qualcosa che Spring Framework impiega con i suoi ReflectionUtils (vedere il makeAccessible(Method)metodo).

Ecco una piccola classe di esempio con un metodo di istanza privato.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

E una classe di esempio che esegue il metodo dell'istanza privata.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Se esegui B, verrà stampato Doing something private. Se ne hai davvero bisogno, la riflessione potrebbe essere utilizzata nei test unitari per accedere ai metodi di istanza privata.


0

Personalmente, ho gli stessi problemi durante il test di metodi privati ​​e questo perché alcuni degli strumenti di test sono limitati. Non è bene che il tuo progetto sia guidato da strumenti limitati se non rispondono alle tue necessità cambiando lo strumento e non il design. Poiché la tua richiesta di C # non posso proporre buoni strumenti di test, ma per Java ci sono due potenti strumenti: TestNG e PowerMock e puoi trovare gli strumenti di test corrispondenti per la piattaforma .NET

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.