Come testare una funzione privata o una classe che ha metodi, campi o classi interne private?


2728

Come posso testare l'unità (usando xUnit) una classe che ha metodi privati ​​interni, campi o classi nidificate? O una funzione che è resa privata dal collegamento interno ( staticin C / C ++) o si trova in uno spazio dei nomi privato ( anonimo )?

Sembra male cambiare il modificatore di accesso per un metodo o una funzione solo per poter eseguire un test.


257
Il modo migliore per testare un metodo privato non è testarlo direttamente
Surya,


383
Non sono d'accordo. Un metodo (pubblico) che è lungo o difficile da comprendere deve essere riformulato. Sarebbe una follia non testare i metodi piccoli (privati) che si ottengono anziché solo quelli pubblici.
Michael Piefel,

181
Non testare alcun metodo solo perché la sua visibilità è stupida. Anche il test unitario dovrebbe riguardare il più piccolo pezzo di codice e se testerai solo metodi pubblici non sarai mai sicuro di sicuro dove si verifica l'errore - quel metodo o qualche altro.
Dainius,

126
Devi testare la funzionalità della classe , non la sua implementazione . Vuoi testare i metodi privati? Prova i metodi pubblici che li chiamano. Se la funzionalità offerta dalla classe è stata testata a fondo, i suoi interni si sono dimostrati corretti e affidabili; non è necessario testare le condizioni interne. I test dovrebbero mantenere il disaccoppiamento dalle classi testate.
Calimar41,

Risposte:


1640

Aggiornare:

Circa 10 anni dopo, forse il modo migliore per testare un metodo privato, o qualsiasi membro inaccessibile, è tramite @Jailbreakil framework Collettore .

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

In questo modo il codice rimane sicuro e leggibile. Nessun compromesso di progettazione, nessun metodo e campi sovraesposti per motivi di test.

Se si dispone in qualche modo di un'applicazione Java legacy e non è consentito modificare la visibilità dei metodi, il modo migliore per testare metodi privati ​​è utilizzare la riflessione .

Internamente stiamo usando gli helper per ottenere / impostare privatee private staticvariabili, nonché invocare privatee private staticmetodi. I seguenti schemi ti permetteranno di fare praticamente qualsiasi cosa relativa ai metodi e ai campi privati. Naturalmente, non è possibile modificare le private static finalvariabili tramite la riflessione.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

E per i campi:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Note:
1. TargetClass.getDeclaredMethod(methodName, argClasses)consente di esaminare i privatemetodi. Lo stesso vale per getDeclaredField.
2. setAccessible(true)È necessario per giocare con i privati.


350
Utile se forse non conosci l'API, ma se devi testare metodi privati ​​in questo modo, c'è qualcosa nel tuo design. Come dice un altro poster, i test unitari dovrebbero testare il contratto della classe: se il contratto è troppo ampio e crea un'istanza troppo del sistema, allora il progetto dovrebbe essere affrontato.
Andygavin,

21
Molto utile. Usandolo è importante tenere presente che fallirebbe gravemente se i test fossero eseguiti dopo l'offuscamento.
Rick Minerich,

20
Il codice di esempio non ha funzionato per me, ma questo ha chiarito meglio le cose: java2s.com/Tutorial/Java/0125__Reflection/…
Rob

35
Molto meglio che usare la riflessione direttamente sarebbe usare una libreria come Powermock .
Michael Piefel,

25
Ecco perché Test Driven Design è utile. Ti aiuta a capire cosa deve essere esposto per convalidare il comportamento.
Thorbjørn Ravn Andersen,

621

Il modo migliore per testare un metodo privato è tramite un altro metodo pubblico. Se ciò non è possibile, allora una delle seguenti condizioni è vera:

  1. Il metodo privato è codice morto
  2. C'è un odore di design vicino alla classe che stai testando
  3. Il metodo che stai tentando di testare non dovrebbe essere privato

6
Inoltre, non ci avviciniamo mai al 100% di copertura del codice, quindi perché non concentrare il tuo tempo a fare test di qualità sui metodi che i clienti utilizzeranno direttamente.
grinch,

30
@grinch Spot on. L'unica cosa che potresti guadagnare dal test dei metodi privati ​​è il debug delle informazioni, ed è a questo che servono i debugger. Se i test del contratto della classe hanno una copertura completa, allora hai tutte le informazioni di cui hai bisogno. I metodi privati ​​sono un dettaglio di implementazione . Se li testerai dovrai cambiare i tuoi test ogni volta che la tua implementazione cambia, anche se il contratto non lo fa. Nell'intero ciclo di vita del software, è probabile che questo costi molto di più rispetto ai vantaggi che offre.
Erik Madsen,

9
@AlexWien hai ragione. La copertura non era il termine giusto. Quello che avrei dovuto dire era "se i tuoi test sul contratto di classe coprono tutti gli input significativi e tutti gli stati significativi". Ciò, giustamente fai notare, potrebbe rivelarsi impossibile testare tramite il codice l'interfaccia pubblica o indirettamente come ti riferisci ad esso. Se questo è il caso di alcuni codici che stai testando, sarei propenso a pensare: (1) Il contratto è troppo generoso (ad es. Troppi parametri nel metodo), oppure (2) Il contratto è troppo vago (ad es. Comportamento del metodo varia notevolmente con lo stato della classe). Considererei entrambi gli odori del design.
Erik Madsen,

16
Quasi tutti i metodi privati ​​non devono essere testati direttamente (per ridurre i costi di manutenzione). Eccezione: i metodi scientifici possono avere forme come f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) dove a, b, c, d, e e f sono espressioni orribilmente complicate ma altrimenti inutili al di fuori di questa singola formula. Alcune funzioni (si pensi all'MD5) sono difficili da invertire, quindi può essere difficile (NP-Hard) scegliere parametri che coprano completamente lo spazio comportamentale. È più semplice testare a, b, c, d, e, f in modo indipendente. Renderli pubblici o privati ​​di pacchetti che siano riutilizzabili. Soluzione: renderli privati, ma testali.
Eponimo

4
@Eponimo Sono un po 'lacerato su questo. Il purista orientato agli oggetti in me direbbe che dovresti usare un approccio orientato agli oggetti piuttosto che metodi privati ​​statici, che ti permetta di testare tramite metodi di istanza pubblica. Ma ancora una volta, in un quadro scientifico, l'overhead della memoria è del tutto probabile che sia una preoccupazione che credo sostenga per l'approccio statico.
Erik Madsen,

323

Quando ho metodi privati ​​in una classe sufficientemente complicati da sentire la necessità di testare direttamente i metodi privati, è un odore di codice: la mia classe è troppo complicata.

Il mio solito approccio per affrontare tali problemi è quello di prendere in giro una nuova classe che contiene i bit interessanti. Spesso questo metodo e i campi con cui interagisce, e forse un altro o due metodi possono essere estratti in una nuova classe.

La nuova classe espone questi metodi come "pubblici", quindi sono accessibili per i test unitari. Le nuove e vecchie classi ora sono entrambe più semplici della classe originale, il che è fantastico per me (devo mantenere le cose semplici o mi perdo!).

Nota che non sto suggerendo che le persone creino lezioni senza usare il cervello! Il punto qui è usare le forze del test unitario per aiutarti a trovare buone nuove classi.


24
La domanda era come testare metodi privati. Dici che faresti una nuova classe per quello (e aggiungerai molta più complessità) e dopo suggeriresti di non creare una nuova classe. Quindi, come testare i metodi privati?
Dainius,

27
@Dainius: non suggerisco di creare una nuova classe solo per poter testare quel metodo. Suggerisco che scrivere test può aiutarti a migliorare il tuo design: i buoni progetti sono facili da testare.
Jay Bazuzi,

13
Ma sei d'accordo sul fatto che un buon OOD esponga (rendere pubblico) solo i metodi necessari per il corretto funzionamento di quella classe / oggetto? tutti gli altri dovrebbero essere privati ​​/ protetti. Quindi in alcuni metodi privati ​​ci sarà un po 'di logica e il test IMO di questi metodi migliorerà solo la qualità del software. Naturalmente sono d'accordo sul fatto che se un pezzo di codice è complesso dovrebbe essere diviso in metodi / classe separati.
Dainius,

6
@Danius: l'aggiunta di una nuova classe, nella maggior parte dei casi riduce la complessità. Basta misurarlo. La testabilità in alcuni casi combatte contro OOD. L'approccio moderno è favorire la testabilità.
AlexWien,

5
Questo è esattamente come utilizzare il feedback dei tuoi test per guidare e migliorare il tuo design! Ascolta i tuoi test. se sono difficili da scrivere, questo ti sta dicendo qualcosa di importante sul tuo codice!
pnschofield,

289

Ho usato la riflessione per fare questo per Java in passato, e secondo me è stato un grosso errore.

A rigor di termini, si dovrebbe non essere scrittura di unit test che mettono alla prova direttamente i metodi privati. Quello che dovresti testare è il contratto pubblico che la classe ha con altri oggetti; non dovresti mai testare direttamente gli interni di un oggetto. Se un altro sviluppatore desidera apportare una piccola modifica interna alla classe, il che non influisce sul contratto pubblico delle classi, deve quindi modificare il test basato sulla riflessione per assicurarsi che funzioni. Se lo fai ripetutamente nel corso di un progetto, i test unitari smettono di essere una misura utile della salute del codice e iniziano a diventare un ostacolo allo sviluppo e un fastidio per il team di sviluppo.

Quello che consiglio invece di fare è usare uno strumento di copertura del codice come Cobertura, per garantire che i test unitari che scrivi forniscano una copertura decente del codice con metodi privati. In questo modo, verifichi indirettamente cosa stanno facendo i metodi privati ​​e mantieni un livello più alto di agilità.


76
+1 a questo. Secondo me è la migliore risposta alla domanda. Testando metodi privati ​​si sta verificando l'implementazione. Ciò vanifica lo scopo del test unitario, che è quello di testare gli input / output di un contratto di classe. Un test deve solo conoscere abbastanza l'attuazione per deridere i metodi che invita le sue dipendenze. Niente di più. Se non puoi modificare l'implementazione senza dover cambiare un test, è probabile che la tua strategia di test sia scadente.
Colin M,

10
@Colin M Non è proprio quello che sta chiedendo però;) Lascialo decidere, non conosci il progetto.
Aaron Marcus,

2
Non proprio vero Potrebbero essere necessari molti sforzi per testare una piccola parte del metodo privato attraverso il metodo pubblico che lo utilizza. Il metodo pubblico potrebbe richiedere una configurazione significativa prima di raggiungere la linea che chiama il metodo privato
ACV

1
Sono d'accordo. È necessario verificare se il contratto è adempiuto. Non dovresti testare come è fatto. Se il contatto viene soddisfatto senza raggiungere il 100% di copertura del codice (in metodi privati), questo potrebbe essere un codice morto o inutile.
wuppi,

207

Da questo articolo: Test dei metodi privati ​​con JUnit e SuiteRunner (Bill Venners), in pratica hai 4 opzioni:

  1. Non testare metodi privati.
  2. Concedi l'accesso al pacchetto metodi.
  3. Utilizzare una classe di test nidificata.
  4. Usa la riflessione.

@JimmyT., Dipende da chi è il "codice di produzione". Chiamerei il codice prodotto per le applicazioni posizionate all'interno della VPN i cui utenti target sono amministratori di sistema come codice di produzione.
Pacerier,

@Pacerier Cosa vuoi dire?
Jimmy T.,

1
5a opzione, come menzionato sopra, sta testando il metodo pubblico che chiama il metodo privato? Corretta?
user2441441

1
@LorenzoLerate Ho lottato con questo perché sto facendo uno sviluppo integrato e vorrei che il mio software fosse il più piccolo possibile. I vincoli di dimensione e le prestazioni sono l'unica ragione che mi viene in mente.

Mi piace molto il test usando la riflessione: qui il metodo Metodo method = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return method.invoke (targetObject, argObjects);
Aguid,

127

Generalmente un test unitario ha lo scopo di esercitare l'interfaccia pubblica di una classe o unità. Pertanto, i metodi privati ​​sono dettagli di implementazione che non ti aspetteresti di testare esplicitamente.


16
Questa è la risposta migliore IMO, o come si dice solitamente, testare il comportamento, non i metodi. I test unitari non sostituiscono le metriche del codice sorgente, gli strumenti di analisi del codice statico e le revisioni del codice. Se i metodi privati ​​sono così complessi che hanno bisogno di separare i test, allora probabilmente devono essere sottoposti a refactoring, non più test lanciati su di esso.
Dan Haynes,

14
quindi non scrivere metodi privati, basta creare altre 10 piccole classi?
rasoio,

1
No, in pratica significa che puoi testare la funzionalità dei metodi privati ​​usando il metodo pubblico che li invoca.
Akshat Sharda,

67

Solo due esempi di dove vorrei testare un metodo privato:

  1. Routine di decrittazione : non vorrei renderle visibili a nessuno solo per motivi di test, altrimenti chiunque può usarle per decrittografare. Ma sono intrinseci al codice, complicati e devono sempre funzionare (l'eccezione evidente è la riflessione che può essere utilizzata per visualizzare anche i metodi privati ​​nella maggior parte dei casi, quando SecurityManagernon è configurato per impedirlo).
  2. Creazione di un SDK per il consumo della comunità. Qui il pubblico assume un significato completamente diverso, dal momento che questo è il codice che l'intero mondo potrebbe vedere (non solo interno alla mia applicazione). Metto il codice in metodi privati ​​se non voglio che gli utenti dell'SDK lo vedano - non lo vedo come odore di codice, ma solo come funziona la programmazione dell'SDK. Ma ovviamente ho ancora bisogno di testare i miei metodi privati ​​e sono lì dove vive la funzionalità del mio SDK.

Capisco l'idea di testare solo il "contratto". Ma non vedo che si possa sostenere che in realtà non testare il codice - il tuo chilometraggio può variare.

Quindi il mio compromesso implica complicare le JUnit con la riflessione, piuttosto che compromettere la mia sicurezza e SDK.


5
Anche se non dovresti mai rendere pubblico un metodo solo per testarlo, privatenon è l'unica alternativa. Nessun modificatore di accesso è package privatee significa che è possibile testarlo a condizione che il test unitario rimanga nello stesso pacchetto.
ngreen

1
Stavo commentando la tua risposta, in particolare il punto 1, non l'OP. Non è necessario rendere un metodo privato solo perché non si desidera che sia pubblico.
ngreen

1
@ngreen true thx - Ero pigro con la parola "pubblico". Ho aggiornato la risposta per includere pubblica, protetta, predefinita (e per menzionare la riflessione). Il punto che stavo cercando di sottolineare è che c'è una buona ragione per cui un po 'di codice è segreto, tuttavia ciò non dovrebbe impedirci di testarlo.
Richard Le Mesurier,

2
Grazie per aver dato esempi concreti a tutti. Molte risposte sono buone, ma dal punto di vista teorico. Nel mondo reale, dobbiamo nascondere l'implementazione dal contratto e dobbiamo ancora testarlo. Leggendo tutti questi, penso che forse questo dovrebbe essere un livello completamente nuovo di test, con un nome diverso
Z. Khullah,

1
Un altro esempio: parser HTML crawler . Dover analizzare un'intera pagina html in oggetti di dominio richiede una tonnellata di logica nitida per convalidare piccole parti della struttura. Ha senso dividerlo in metodi e chiamarlo da un metodo pubblico, ma non ha senso avere tutti i metodi più piccoli che lo compongono siano pubblici, né ha molto senso creare 10 classi per pagina HTML.
Nether

59

I metodi privati ​​sono chiamati con un metodo pubblico, quindi gli input per i tuoi metodi pubblici dovrebbero anche testare i metodi privati ​​chiamati da tali metodi pubblici. Quando un metodo pubblico fallisce, potrebbe trattarsi di un errore nel metodo privato.


Fatto reale. Il problema è quando l'implementazione è più complicata, come se un metodo pubblico chiama diversi metodi privati. Quale ha fallito? O se si tratta di un metodo privato complicato, che non può essere esposto né trasferito in una nuova classe
Z. Khullah

Hai già menzionato il motivo per cui il metodo privato dovrebbe essere testato. Hai detto "allora potrebbe essere", quindi non sei sicuro. IMO, se un metodo debba essere testato è ortogonale al suo livello di accesso.
Wei Qiu,

39

Un altro approccio che ho usato è quello di cambiare un metodo privato per impacchettare privato o protetto, quindi completarlo con l' annotazione @VisibleForTesting della libreria di Google Guava.

Questo dirà a chiunque usi questo metodo di prestare attenzione e di non accedervi direttamente nemmeno in un pacchetto. Inoltre, una classe di test non deve necessariamente trovarsi nello stesso pacchetto fisicamente , ma nello stesso pacchetto nella cartella test .

Ad esempio, se è presente un metodo da testare, è src/main/java/mypackage/MyClass.javanecessario inserire la chiamata di prova src/test/java/mypackage/MyClassTest.java. In questo modo, hai avuto accesso al metodo di test nella tua classe di test.


1
Non sapevo di questo, è interessante, penso ancora che se hai bisogno di quel tipo di annottaion hai problemi di progettazione.
Seb

2
Per me, è come dire invece di dare una chiave una tantum al tuo vigile del fuoco per testare l'esercitazione antincendio, lasci la tua porta aperta sbloccata con un cartello sulla porta che dice - "sbloccata per il test - se non vieni da la nostra compagnia, per favore non entrare ".
P. Jeremy Krieg,

@FrJeremyKrieg - cosa significa?
MasterJoe2

1
@ MasterJoe2: lo scopo di un test antincendio è migliorare la sicurezza dell'edificio contro il rischio di incendio. Ma devi dare al guardiano l'accesso al tuo edificio. Se si lascia definitivamente sbloccata la porta d'ingresso, si aumenta il rischio di accesso non autorizzato e la si rende meno sicura. Lo stesso vale per il modello VisibleForTesting: aumenti il ​​rischio di accesso non autorizzato al fine di ridurre il rischio di "incendio". Meglio concedere l'accesso come una tantum per i test solo quando è necessario (ad esempio, usando la riflessione) piuttosto che lasciarlo sbloccato permanentemente (non privato).
P. Jeremy Krieg,

34

Per testare il codice legacy con classi grandi e stravaganti, è spesso molto utile essere in grado di testare l'unico metodo privato (o pubblico) che sto scrivendo in questo momento .

Uso junitx.util.PrivateAccessor -package per Java. Un sacco di utili linee guida per l'accesso a metodi privati ​​e campi privati.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

2
Assicurati di scaricare l'intero pacchetto JUnit-addons ( sourceforge.net/projects/junit-addons ), non il progetto PrivateAccessor consigliato da Source Forge.
skia.heliou,

2
E assicurati che, quando usi un Classcome primo parametro in questi metodi, accedi solo ai staticmembri.
skia.heliou,

31

In Spring Framework puoi testare metodi privati ​​usando questo metodo:

ReflectionTestUtils.invokeMethod()

Per esempio:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

La soluzione più pulita e più concisa finora, ma solo se stai usando Spring.
Denis Nikolaenko,

28

Avendo provato la soluzione di Cem Catikkas usando reflection per Java, dovrei dire che era una soluzione più elegante di quella che ho descritto qui. Tuttavia, se stai cercando un'alternativa all'utilizzo di reflection e hai accesso alla fonte che stai testando, questa sarà comunque un'opzione.

È possibile il merito di testare metodi privati ​​di una classe, in particolare con lo sviluppo test-driven , in cui si desidera progettare piccoli test prima di scrivere qualsiasi codice.

La creazione di un test con accesso a membri e metodi privati ​​può testare aree di codice che sono difficili da indirizzare specificamente con accesso solo a metodi pubblici. Se un metodo pubblico prevede diversi passaggi, può consistere in diversi metodi privati, che possono quindi essere testati singolarmente.

vantaggi:

  • Può testare una granularità più fine

svantaggi:

  • Il codice di prova deve risiedere nello stesso file del codice sorgente, che può essere più difficile da mantenere
  • Analogamente ai file di output .class, devono rimanere all'interno dello stesso pacchetto dichiarato nel codice sorgente

Tuttavia, se i test continui richiedono questo metodo, potrebbe essere un segnale che i metodi privati ​​dovrebbero essere estratti, che potrebbero essere testati in modo tradizionale e pubblico.

Ecco un esempio contorto di come funzionerebbe:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

La classe interna verrebbe compilata ClassToTest$StaticInnerTest.

Vedi anche: Java Suggerimento 106: classi interne statiche per divertimento e profitto


26

Come altri hanno già detto ... non testare direttamente i metodi privati. Ecco alcuni pensieri:

  1. Mantieni tutti i metodi piccoli e mirati (facile da testare, facile da trovare cosa non va)
  2. Utilizzare gli strumenti di copertura del codice. Mi piace Cobertura (oh buon giorno, sembra che sia disponibile una nuova versione!)

Eseguire la copertura del codice sui test unitari. Se vedi che i metodi non sono completamente testati aggiungi ai test per aumentare la copertura. Cerca la copertura del codice al 100%, ma ti rendi conto che probabilmente non lo otterrai.


Per la copertura del codice. Indipendentemente dal tipo di logica presente nel metodo privato, stai comunque invocando tale logica tramite un metodo pubblico. Uno strumento di copertura del codice può mostrarti quali parti sono coperte dal test, quindi puoi vedere se il tuo metodo privato è testato.
coolcfan,

Ho visto classi in cui l'unico metodo pubblico è main [], e fanno comparire la GUI e collegano il database e un paio di server web in tutto il mondo. Facile dire "non testare indirettamente" ...
Audrius Meskauskas,

26

I metodi privati ​​sono utilizzati da quelli pubblici. Altrimenti, sono codici morti. Ecco perché testate il metodo pubblico, affermando i risultati attesi del metodo pubblico e, quindi, i metodi privati ​​che consuma.

I test sui metodi privati ​​devono essere testati eseguendo il debug prima di eseguire i test unitari su metodi pubblici.

Possono anche essere sottoposti a debug utilizzando lo sviluppo guidato dai test, eseguendo il debug dei test delle unità fino a quando non vengono soddisfatte tutte le asserzioni.

Personalmente credo che sia meglio creare classi usando TDD; creando gli stub del metodo pubblico, quindi generando unit test con tutte le asserzioni definite in anticipo, quindi il risultato atteso del metodo viene determinato prima di codificarlo. In questo modo, non seguire la strada sbagliata per far sì che le affermazioni del test unitario si adattino ai risultati. La tua classe è quindi robusta e soddisfa i requisiti quando tutti i test unitari superano.


2
Anche se questo è vero, può portare ad alcuni test molto complicati: è un modello migliore per testare un singolo metodo alla volta (Unit testing) piuttosto che un intero gruppo di essi. Non è un suggerimento terribile, ma penso che ci siano risposte migliori qui - questa dovrebbe essere l'ultima risorsa.
Bill K,

24

Se si utilizza Spring, ReflectionTestUtils offre alcuni strumenti utili che aiutano qui con il minimo sforzo. Ad esempio, per impostare una derisione su un membro privato senza essere costretto ad aggiungere un setter pubblico indesiderato:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

Se stai provando a testare il codice esistente che sei riluttante o incapace di cambiare, la riflessione è una buona scelta.

Se il design della classe è ancora flessibile e hai un metodo privato complicato che vorresti testare separatamente, ti suggerisco di estrarlo in una classe separata e testare quella classe separatamente. Questo non deve cambiare l'interfaccia pubblica della classe originale; può creare internamente un'istanza della classe helper e chiamare il metodo helper.

Se si desidera verificare condizioni di errore difficili derivanti dal metodo helper, è possibile fare un ulteriore passo avanti. Estrarre un'interfaccia dalla classe helper, aggiungere un getter pubblico e setter alla classe originale per iniettare la classe helper (utilizzata attraverso la sua interfaccia), quindi iniettare una versione finta della classe helper nella classe originale per testare come la classe originale risponde alle eccezioni dell'assistente. Questo approccio è utile anche se si desidera testare la classe originale senza testare anche la classe helper.


19

Il test di metodi privati ​​interrompe l'incapsulamento della classe perché ogni volta che si modifica l'implementazione interna si interrompe il codice client (in questo caso, i test).

Quindi non testare metodi privati.


4
unit test e codice src sono una coppia. Se si modifica l'src, forse è necessario modificare il test unitario. Questo è il senso del test junit. Garantiranno che tutto funziona come prima. e va bene se si rompono, se si cambia il codice.
AlexWien,

1
Non concordare sul fatto che il test unitario dovrebbe cambiare se il codice cambia. Se ti viene ordinato di aggiungere funzionalità a una classe senza modificare la funzionalità esistente della classe, i test unitari esistenti dovrebbero passare anche dopo aver modificato il codice. Come menziona Peter, i test unitari dovrebbero testare l'interfaccia, non come è fatto internamente. In Test Driven Development i test unitari vengono creati prima della scrittura del codice, per concentrarsi sull'interfaccia della classe, non su come viene risolto internamente.
Harald Coppoolse,

16

La risposta dalla pagina FAQ di JUnit.org :

Ma se devi ...

Se si utilizza JDK 1.3 o versione successiva, è possibile utilizzare reflection per sovvertire il meccanismo di controllo dell'accesso con l'aiuto di PrivilegedAccessor . Per i dettagli su come usarlo, leggi questo articolo .

Se si utilizza JDK 1.6 o versioni successive e si annotano i test con @Test, è possibile utilizzare Dp4j per iniettare la riflessione nei metodi di test. Per i dettagli su come usarlo, vedere questo script di prova .

PS io sono il principale contributore al Dp4j , chiedo a me se hai bisogno di aiuto. :)


16

Se si desidera testare metodi privati ​​di un'applicazione legacy in cui non è possibile modificare il codice, un'opzione per Java è jMockit , che consente di creare simulazioni su un oggetto anche quando sono private per la classe.


4
Come parte della libreria jmockit, hai accesso alla classe Deencapsulation che semplifica il test dei metodi privati: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

Uso sempre questo approccio. Molto utile
MedicineMan,

14

Tendo a non testare metodi privati. Qui sta la follia. Personalmente, credo che dovresti testare solo le tue interfacce esposte pubblicamente (e questo include metodi protetti e interni).


14

Se stai usando JUnit, dai un'occhiata a junit-addons . Ha la capacità di ignorare il modello di sicurezza Java e accedere a metodi e attributi privati.


13

Ti suggerirei un po 'di refactoring del codice. Quando devi iniziare a pensare a usare reflection o altri tipi di cose, solo per testare il tuo codice, qualcosa non va nel tuo codice.

Hai menzionato diversi tipi di problemi. Cominciamo con campi privati. In caso di campi privati ​​avrei aggiunto un nuovo costruttore e iniettato campi in quello. Invece di questo:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Avrei usato questo:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Questo non sarà un problema anche con un codice legacy. Il vecchio codice utilizzerà un costruttore vuoto e, se me lo chiedi, il codice refactored apparirà più pulito e sarai in grado di iniettare i valori necessari nel test senza riflessione.

Ora sui metodi privati. Nella mia esperienza personale quando devi stub un metodo privato per il test, quel metodo non ha nulla a che fare in quella classe. Un modello comune, in quel caso, sarebbe avvolgerlo all'interno di un'interfaccia, come Callablee quindi si passa a quell'interfaccia anche nel costruttore (con quel trucco costruttore multiplo):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

Per lo più tutto ciò che ho scritto sembra essere un modello di iniezione di dipendenza. Nella mia esperienza personale è molto utile durante i test e penso che questo tipo di codice sia più pulito e sarà più facile da mantenere. Direi lo stesso delle classi nidificate. Se una classe nidificata contiene una logica pesante, sarebbe meglio se l'avessi spostata come classe privata del pacchetto e l'avessi iniettata in una classe che ne aveva bisogno.

Ci sono anche molti altri modelli di progettazione che ho usato durante il refactoring e la manutenzione del codice legacy, ma tutto dipende dai casi del codice da testare. L'uso della riflessione per lo più non è un problema, ma quando si dispone di un'applicazione aziendale che è pesantemente testata e i test vengono eseguiti prima di ogni distribuzione, tutto diventa molto lento (è solo fastidioso e non mi piace quel tipo di cose).

C'è anche l'iniezione del setter, ma non consiglierei di usarlo. Meglio restare con un costruttore e inizializzare tutto quando è veramente necessario, lasciando la possibilità di iniettare le dipendenze necessarie.


1
Non sono d'accordo con l' ClassToTest(Callable)iniezione. Questo rende ClassToTestpiù complicato. Mantienilo semplice. Inoltre, ciò richiede che qualcun altro dica ClassToTestqualcosa di cui ClassToTestdovrebbe essere il capo. C'è un posto per iniettare la logica, ma non è così. Hai appena reso la classe più difficile da mantenere, non più facile.
Loduwijk

Inoltre, è preferibile che il tuo metodo di test Xnon aumenti la Xcomplessità al punto che potrebbe causare più problemi ... e quindi richiedere test aggiuntivi ... che se hai implementato in un modo che può causare più problemi .. (questo non è un loop infinito; ogni iterazione è probabilmente più piccola di quella precedente, ma comunque fastidiosa)
Loduwijk

2
Potresti spiegare perché rendere la classe ClassToTestpiù difficile da mantenere? In realtà semplifica la manutenzione della tua applicazione, cosa suggerisci di creare una nuova classe per ogni diverso valore di cui hai bisogno firste delle variabili "seconde"?
GROX13

1
Il metodo di prova non aumenta la complessità. È solo la tua classe che è scritta male, così male che non può essere testata.
GROX13

Più difficile da mantenere perché la classe è più complicata. class A{ A(){} f(){} }è più semplice di class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }. Se ciò non fosse vero, e se fosse vero che fornire la logica di una classe come argomenti del costruttore fosse più facile da mantenere, allora passeremmo tutta la logica come argomenti del costruttore. Semplicemente non vedo come si possa affermare class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }che rende A più facile da mantenere. Ci sono casi in cui iniettare in questo modo ha senso, ma non vedo il tuo esempio come "più facile da mantenere"
Loduwijk

12

È possibile accedere a un metodo privato solo all'interno della stessa classe. Quindi non c'è modo di testare un metodo "privato" di una classe target da qualsiasi classe di test. Una soluzione è che puoi eseguire test unitari manualmente o cambiare il tuo metodo da "privato" a "protetto".

Quindi è possibile accedere a un metodo protetto solo all'interno dello stesso pacchetto in cui è definita la classe. Quindi, testare un metodo protetto di una classe target significa che dobbiamo definire la tua classe test nello stesso pacchetto della classe target.

Se tutto quanto sopra non soddisfa le tue esigenze, utilizza il modo di riflessione per accedere al metodo privato.


Stai mescolando "protetto" con "amichevole". Un metodo protetto è accessibile solo da una classe i cui oggetti sono assegnabili alla classe target (es. Sottoclassi).
Sayo Oladeji,

1
In realtà non esiste un "amico" in Java, il termine è "pacchetto" ed è indicato dalla mancanza di un modificatore privato / pubblico / protetto. Avrei appena corretto questa risposta, ma ce n'è davvero una buona che lo dice già, quindi consiglierei di cancellarla.
Bill K,

Ho quasi declassato, pensando sempre "Questa risposta è semplicemente sbagliata". fino ad arrivare all'ultima frase, che contraddice il resto della risposta. L'ultima frase avrebbe dovuto essere la prima.
Loduwijk

11

Come molti hanno suggerito sopra, un buon modo è testarli attraverso le tue interfacce pubbliche.

Se lo fai, è una buona idea usare uno strumento di copertura del codice (come Emma) per vedere se i tuoi metodi privati ​​sono effettivamente eseguiti dai tuoi test.


Non dovresti testare indirettamente! Non solo toccando tramite copertura; prova che il risultato atteso sia consegnato!
AlexWien,

11

Ecco la mia funzione generica per testare i campi privati:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

10

Vedi sotto per un esempio;

È necessario aggiungere la seguente dichiarazione di importazione:

import org.powermock.reflect.Whitebox;

Ora puoi passare direttamente l'oggetto che ha il metodo privato, il nome del metodo da chiamare e parametri aggiuntivi come di seguito.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

Oggi ho spinto una libreria Java per aiutare a testare metodi e campi privati. È stato progettato pensando ad Android, ma può davvero essere utilizzato per qualsiasi progetto Java.

Se hai del codice con metodi o campi privati ​​o costruttori, puoi usare BoundBox . Fa esattamente quello che stai cercando. Di seguito è riportato un esempio di test che accede a due campi privati ​​di un'attività Android per testarlo:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox semplifica il test di campi, metodi e costruttori privati ​​/ protetti. Puoi persino accedere a elementi nascosti per eredità. In effetti, BoundBox rompe l'incapsulamento. Ti darà accesso a tutto ciò attraverso la riflessione, MA tutto viene controllato al momento della compilazione.

È ideale per testare alcuni codici legacy. Usalo attentamente. ;)

https://github.com/stephanenicolas/boundbox


1
Appena provato, BoundBox è una soluzione semplice ed elegante e!
forhas

9

Innanzitutto, porterò questa domanda: perché i tuoi membri privati ​​hanno bisogno di test isolati? Sono così complessi, fornendo comportamenti così complicati da richiedere test a parte la superficie pubblica? È un test unitario, non un test "line-of-code". Non sudare le piccole cose.

Se sono così grandi, abbastanza grandi che questi membri privati ​​sono ciascuno una "unità" di grande complessità - considera di rifattorizzare tali membri privati ​​fuori da questa classe.

Se il refactoring è inappropriato o non fattibile, è possibile utilizzare il modello di strategia per sostituire l'accesso a queste funzioni / classi di membri privati ​​durante il test unitario? Sotto test unitari, la strategia fornirebbe una validazione aggiuntiva, ma nelle build di rilascio sarebbe un semplice passaggio.


3
Perché spesso un particolare pezzo di codice da un metodo pubblico viene trasformato in un metodo privato interno ed è davvero la parte critica della logica che potresti aver sbagliato. Vuoi testarlo indipendentemente dal metodo pubblico
oxbow_lakes,

Anche il codice più corto a volte senza unit test non è corretto. Prova solo a chiarire la differenza tra 2 angoli geografici. 4 righe di codice, e la maggior parte non lo farà correttamente al primo tentativo. Tali metodi richiedono un test unitario, poiché formano la base di un codice attendibile. (Anche questo codice utile può essere pubblico; meno utile protetto
AlexWien

8

Di recente ho avuto questo problema e ho scritto un piccolo strumento, chiamato Picklock , che evita i problemi dell'utilizzo esplicito dell'API di riflessione Java, due esempi:

Metodi di chiamata, ad es. private void method(String s)- riflessione Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Metodi di chiamata, ad es private void method(String s). Tramite Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Impostazione dei campi, ad es private BigInteger amount;. Mediante riflessione Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Impostazione dei campi, ad es private BigInteger amount;. Tramite Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

7

PowerMockito è fatto per questo. Usa la dipendenza da forno

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Quindi puoi farlo

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
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.