Come posso testare una funzione privata o una class che ha metodi privati, campi o classi interne?

Come posso testare unitamente (usando xUnit) una class che ha metodi privati ​​interni, campi o classi nidificate? O una funzione resa privata da un collegamento interno ( static in C / C ++) o in uno spazio dei nomi privato ( anonimo )?

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

Se si dispone di un’applicazione legacy Java e non è ansible modificare la visibilità dei metodi, il modo migliore per testare i metodi privati ​​consiste nell’utilizzare la reflection .

Internamente utilizziamo helper per ottenere / impostare variabili private static private e private static e invocare metodi private static private e private static . I seguenti schemi ti permetteranno di fare praticamente qualsiasi cosa relativa ai metodi e ai campi privati. Ovviamente non è ansible modificare private static final variabili private static final attraverso 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); 

Gli appunti:
1. targetClass.getDeclaredMethod(methodName, argClasses) consente di esaminare private metodi private . La stessa cosa vale per getDeclaredField .
2. È necessario setAccessible(true) per giocare con i privati.

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

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

Quando ho metodi privati ​​in una class sufficientemente complicata che sento il bisogno di testare direttamente i metodi privati, questo è un odore di codice: la mia class è troppo complicata.

Il mio approccio abituale nell’affrontare questi problemi è quello di stuzzicare una nuova class che contiene i bit interessanti. Spesso, questo metodo e i campi con cui interagisce, e forse un altro metodo o due possono essere estratti in una nuova class.

La nuova class espone questi metodi come “pubblici”, quindi sono accessibili per i test unitari. Le nuove e le vecchie classi ora sono entrambe più semplici della class originale, il che è ottimo per me (ho bisogno di mantenere le cose semplici, o mi perdo!).

Nota che non sto suggerendo che le persone creano lezioni senza usare il cervello! Il punto qui è utilizzare le forze dell’unità di test per aiutarti a trovare nuove buone classi.

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

A rigor di termini, non dovresti scrivere test unitari che testino direttamente i metodi privati. Quello che dovresti provare è il contratto pubblico che la class ha con altri oggetti; non dovresti mai testare direttamente le parti interne di un object. Se un altro sviluppatore vuole apportare una piccola modifica interna alla class, che non influisce sul contratto pubblico delle classi, allora deve modificare il test basato sulla riflessione per assicurarsi che funzioni. Se lo fai ripetutamente durante un progetto, i test unitari smettono di essere un’utile misura della salute del codice, e iniziano a diventare un ostacolo allo sviluppo e un fastidio per il team di sviluppo.

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

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

  • Non testare metodi privati.
  • Fornire l’accesso al pacchetto metodi.
  • Utilizzare una class di test annidata.
  • Usa la riflessione.

Generalmente un test unitario è destinato all’esercizio dell’interfaccia pubblica di una class o unità. Pertanto, i metodi privati ​​sono dettagli di implementazione che non ti aspetti di testare esplicitamente.

Solo due esempi di dove vorrei testare un metodo privato:

  1. Procedure di decrittografia : non vorrei renderle visibili a chiunque da vedere solo per motivi di testing, altrimenti chiunque può usarle per decodificare. Ma sono intrinseche al codice, complicate e devono sempre funzionare (l’ovvia eccezione è la riflessione che può essere utilizzata per visualizzare anche i metodi privati ​​nella maggior parte dei casi, quando SecurityManager non è configurato per impedirlo).
  2. Creazione di un SDK per il consumo della comunità. Qui il pubblico assume un significato completamente diverso, poiché questo è un codice che il mondo intero può vedere (non solo interno alla mia applicazione). Inserisco il codice in metodi privati ​​se non voglio che gli utenti dell’SDK lo vedano – non lo vedo come un odore di codice, ma semplicemente 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 uno possa difendere in realtà non testando il codice – il tuo chilometraggio può variare.

Quindi il mio compromesso consiste nel complicare le JUnit con la riflessione, piuttosto che compromettere la sicurezza e l’SDK.

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

Un altro approccio che ho utilizzato è quello di modificare un metodo privato per il pacchetto privato o protetto, quindi completarlo con l’annotazione @VisibleForTesting della libreria Google Guava.

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

Ad esempio, se un metodo da testare è in src/main/java/mypackage/MyClass.java la chiamata di test deve essere collocata in src/test/java/mypackage/MyClassTest.java . In questo modo, hai avuto accesso al metodo di test nella tua class di test.

Per testare il codice legacy con classi grandi e bizzarre, è spesso molto utile poter testare il metodo privato (o pubblico) che sto scrivendo in questo momento .

Io uso il pacchetto junitx.util.PrivateAccessor per Java. Un sacco di utili one-liner per accedere 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); 

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

È ansible valutare i metodi privati ​​di una class, in particolare con lo sviluppo basato su test , 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 raggiungere specificamente con l’accesso solo a metodi pubblici. Se un metodo pubblico prevede diversi passaggi, può essere costituito da diversi metodi privati, che possono quindi essere testati individualmente.

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 il test continuo richiede questo metodo, potrebbe essere un segnale che i metodi privati ​​dovrebbero essere estratti, che potrebbero essere testati nel 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 class interna verrebbe compilata in ClassToTest$StaticInnerTest .

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

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, trovare facilmente ciò che è sbagliato)
  2. Utilizzare gli strumenti di copertura del codice. Mi piace Cobertura (oh buon giorno, sembra che una nuova versione è fuori!)

Esegui la copertura del codice sui test dell’unità. Se vedi che i metodi non sono completamente testati, aggiungi ai test per ottenere la copertura. Mira alla copertura del 100% del codice, ma renditi conto che probabilmente non lo capirai.

I metodi privati ​​sono consumati da metodi pubblici. Altrimenti, sono codice morto. Ecco perché testare il metodo pubblico, affermando i risultati attesi del metodo pubblico e, di conseguenza, i metodi privati ​​che consuma.

I test dei metodi privati ​​devono essere testati tramite il debugging prima di eseguire i test unitari sui metodi pubblici.

Possono anche essere sottoposti a debug utilizzando lo sviluppo basato su test, eseguendo il debug dei test delle unità finché tutte le asserzioni non vengono soddisfatte.

Personalmente ritengo sia meglio creare classi usando TDD; creando gli stub del metodo pubblico, quindi generando i test unitari con tutte le asserzioni definite in anticipo, in modo che il risultato atteso del metodo sia determinato prima di codificarlo. In questo modo, non si imbocca la strada sbagliata per far sì che le asserzioni del test unitario corrispondano ai risultati. La tua class è quindi solida e soddisfa i requisiti quando passano tutti i tuoi test unitari.

Se si utilizza Spring, ReflectionTestUtils fornisce alcuni strumenti utili che aiutano in questo modo con il minimo sforzo. Ad esempio, per impostare una simulazione su un membro privato senza essere costretti ad aggiungere un setter pubblico indesiderato:

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

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

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

Se vuoi testare condizioni di errore difficili provenienti dal metodo helper, puoi fare un ulteriore passo avanti. Estrai un’interfaccia dalla class helper, aggiungi un getter pubblico e un setter alla class originale per iniettare la class helper (usata attraverso la sua interfaccia), e poi inietta una versione mock della class helper nella class originale per testare come la class originale risponde alle eccezioni dall’assistente. Questo approccio è utile anche se si desidera testare la class originale senza testare la class helper.

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

Quindi non testare metodi privati.

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

 ReflectionTestUtils.invokeMethod() 

Per esempio:

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

Se si desidera testare metodi privati ​​di un’applicazione legacy in cui non è ansible modificare il codice, un’opzione per Java è jMockit , che consente di creare mock su un object anche quando sono privati ​​della class.

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.

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

La risposta dalla pagina delle FAQ di JUnit.org :

Ma se devi …

Se si utilizza JDK 1.3 o versione successiva, è ansible utilizzare reflection per sovvertire il meccanismo di controllo degli accessi con l’ausilio di PrivilegedAccessor . Per i dettagli su come usarlo, leggi questo articolo .

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

PS Sono il principale contributore di Dp4j , chiedimi se hai bisogno di aiuto. 🙂

È necessario accedere a un metodo privato all’interno della stessa class. Quindi non c’è modo di testare un metodo “privato” di una class target da qualsiasi class di test. Una via d’uscita è che puoi eseguire il test dell’unità manualmente o puoi cambiare il tuo metodo da “privato” a “protetto”.

E quindi è ansible accedere a un metodo protetto all’interno dello stesso pacchetto in cui è definita la class. Quindi, testare un metodo protetto di una class di destinazione significa che dobbiamo definire la tua class di test nello stesso pacchetto della class di destinazione.

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

Ti suggerirei di rifare un po ‘il tuo codice. Quando devi iniziare a pensare all’utilizzo della riflessione o di altro tipo, per testare il tuo codice, qualcosa non funziona nel tuo codice.

Hai citato diversi tipi di problemi. Iniziamo con i campi privati. In caso di campi privati ​​avrei aggiunto un nuovo costruttore e campi iniettati in quello. Invece di questo:

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

Avrei usato questo:

 public class ClassToTest { private final String first; private final List second; public ClassToTest() { this("first", new ArrayList<>()); } public ClassToTest(final String first, final List second) { this.first = first; this.second = second; } ... } 

Questo non sarà un problema anche con qualche codice legacy. Il vecchio codice userà 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 riflettere.

Adesso sui metodi privati. Nella mia esperienza personale, quando si deve stubare un metodo privato per il test, allora quel metodo non ha nulla a che fare in quella class. Un modello comune, in questo caso, sarebbe quello di racchiuderlo all’interno di un’interfaccia, come Callable e quindi passare quell’interfaccia anche nel costruttore (con quel trucco con più costruttori):

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

Per lo più tutto ciò che ho scritto sembra un modello di iniezione dipendente. Nella mia esperienza personale è molto utile durante i test, e penso che questo tipo di codice sia più pulito e sia più facile da mantenere. Direi lo stesso per le classi annidate. Se una class nidificata contiene una logica pesante, sarebbe meglio se lo avessi spostato come una class privata del pacchetto e l’avessi iniettato in una class che ne aveva bisogno.

Ci sono anche molti altri modelli di progettazione che ho usato durante il refactoring e il mantenimento del codice legacy, ma tutto dipende dai casi del tuo codice da testare. L’uso della riflessione non è un problema, ma quando si dispone di un’applicazione aziendale sottoposta a test intensivi e di test eseguiti prima di ogni distribuzione, tutto diventa molto lento (è solo fastidioso e non mi piace quel genere di cose).

There is also setter injection, but I wouldn’t recommended using it. I’d better stick with a constructor and initialize everything when it’s really necessary, leaving the possibility for injecting necessary dependencies.

As many above have suggested, a good way is to test them via your public interfaces.

If you do this, it’s a good idea to use a code coverage tool (like Emma) to see if your private methods are in fact being executed from your tests.

Here is my generic function to test private fields:

 protected  F getPrivateField(String fieldName, Object obj) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return (F)field.get(obj); } 

Please see below for an example;

The following import statement should be added:

 import org.powermock.reflect.Whitebox; 

Now you can directly pass the object which has the private method, method name to be called, and additional parameters as below.

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

Today, I pushed a Java library to help testing private methods and fields. It has been designed with Android in mind, but it can really be used for any Java project.

If you got some code with private methods or fields or constructors, you can use BoundBox . It does exactly what you are looking for. Here below is an example of a test that accesses two private fields of an Android activity to test it:

 @UiThreadTest public void testCompute() { // Given boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity()); // When boundBoxOfMainActivity.boundBox_getButtonMain().performClick(); // Then assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText()); } 

BoundBox makes it easy to test private/protected fields, methods and constructors. You can even access stuff that is hidden by inheritance. Indeed, BoundBox breaks encapsulation. It will give you access to all that through reflection, BUT everything is checked at compile time.

It is ideal for testing some legacy code. Use it carefully. 😉

https://github.com/stephanenicolas/boundbox

First, I’ll throw this question out: Why do your private members need isolated testing? Are they that complex, providing such complicated behaviors as to require testing apart from the public surface? It’s unit testing, not ‘line-of-code’ testing. Don’t sweat the small stuff.

If they are that big, big enough that these private members are each a ‘unit’ large in complexity — consider refactoring such private members out of this class.

If refactoring is inappropriate or infeasible, can you use the strategy pattern to replace access to these private member functions / member classs when under unit test? Under unit test, the strategy would provide added validation, but in release builds it would be simple passthrough.

I recently had this problem and wrote a little tool, called Picklock , that avoids the problems of explicitly using the Java reflection API, two examples:

Calling methods, eg private void method(String s) – by Java reflection

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

Calling methods, eg private void method(String s) – by Picklock

 interface Accessible { void method(String s); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.method("mystring"); 

Setting fields, eg private BigInteger amount; – by Java reflection

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

Setting fields, eg private BigInteger amount; – by Picklock

 interface Accessible { void setAmount(BigInteger amount); } ... Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class); a.setAmount(BigInteger.valueOf(42)); 

For Java I’d use reflection , since I don’t like the idea of changing the access to a package on the declared method just for the sake of testing. However, I usually just test the public methods which should also ensure the private methods are working correctly.

you can’t use reflection to get private methods from outside the owner class, the private modifier affects reflection also

Questo non è vero. You most certainly can, as mentioned in Cem Catikkas’s answer .