Come faccio a testare una funzione privata o di una classe con metodi privati, campi o classi interne?

StackOverflow https://stackoverflow.com/questions/34571

  •  09-06-2019
  •  | 
  •  

Domanda

Come faccio a test di unità (utilizzando xUnit) una classe che ha privato interno, metodi, campi o classi nidificate?O una funzione che è fatto privato, da avere collegamento interno (static in C/C++) o in un privato (anonimo spazio dei nomi?

Sembra di male a cambiare il modificatore di accesso per un metodo o una funzione solo per essere in grado di eseguire un test.

È stato utile?

Soluzione

Aggiornamento:

Circa 10 anni più tardi, forse, il modo migliore per testare un metodo privato, o inaccessibile membro, via @Jailbreak dal Collettore quadro.

@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 type-safe e leggibile.Non sono compromessi, non sovraesporre metodi e campi per il bene di test.

Se avete un po ' di eredità Java applicazione, e non si può modificare la visibilità dei tuoi metodi, il modo migliore per testare i metodi privati è quello di utilizzare riflessione.

Internamente stiamo usando aiutanti per ottenere/impostare private e private static variabili e richiamare private e private static i metodi.I seguenti modelli consente di fare praticamente tutto ciò che riguarda il privato, metodi e campi.Naturalmente, non si può cambiare private static final le variabili 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);

Note:
1. TargetClass.getDeclaredMethod(methodName, argClasses) consente di guardare in private i metodi.La stessa cosa vale per getDeclaredField.
2.Il setAccessible(true) è necessario per giocare con i privati.

Altri suggerimenti

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

  1. Il metodo privato è morto il codice
  2. C'è un disegno odore vicino alla classe che si sta verificando
  3. Il metodo che si sta tentando di prova non deve essere privato

Quando ho privato metodi in una classe sufficientemente complicato che sento il bisogno di testare i metodi privati direttamente, che è un codice odore:la mia classe è troppo complicato.

Il mio solito approccio per affrontare questi problemi è quello di prendere in giro una nuova classe che contiene le parti interessanti.Spesso, questo metodo e i settori in cui interagisce con, e forse un altro metodo o due può essere estratto in una nuova classe.

La nuova classe espone questi metodi come 'pubblico', in modo che siano accessibili per i test di unità.Il nuovo e il vecchio classi sono ora sia più semplice rispetto all'originale classe, che è grande per me (ho bisogno di mantenere le cose semplici, o mi sono perso!).

Nota che non sto suggerendo che la gente di creare classi, senza usare il cervello!Il punto qui è quello di utilizzare le forze di unit test per aiutarvi a trovare buone nuove classi.

Ho usato riflessione per fare questo per Java in passato, e a mio parere è stato un grosso errore.

A rigor di termini, si dovrebbe non scrivere unit test per testare direttamente i metodi privati.Che cosa è dovrebbe prova il contratto di appalto pubblico che la classe ha con gli altri oggetti;non si dovrebbe mai provare direttamente un oggetto interno.Se uno sviluppatore vuole fare una piccola modifica interna alla classe, che non interessa le classi di appalto pubblico, lui/lei ha quindi modificare la vostra riflessione a base di prova per assicurarsi che funziona.Se fate questo più volte nel corso di un progetto, test di unità per poi smettere di essere un utile di misurazione del codice di salute, e diventare un ostacolo allo sviluppo, e un fastidio al team di sviluppo.

Cosa mi consiglia di fare, invece, è l'utilizzo di un strumento di copertura del codice, come Cobertura, per garantire che l'unità di test di scrittura di fornire una decente copertura del codice in metodi privati.In questo modo, indirettamente prova che i metodi privati stanno facendo, e di mantenere un elevato livello di agilità.

Da questo articolo: Test Metodi Privati con JUnit e SuiteRunner (Bill Venners), che, fondamentalmente, sono 4 opzioni:

  • Non testare i metodi privati.
  • Dare i metodi di accesso pacchetto.
  • Utilizzare un nidificati classe di test.
  • Utilizzare la reflection.

Generalmente una unità di test è destinato ad esercitare l'interfaccia pubblica di una classe o di una unità.Pertanto, i metodi privati sono dettagli di implementazione, che non ci si aspetterebbe di test in modo esplicito.

Solo due esempi di cui vorrei testare un metodo privato:

  1. Routine di decrittografia - Io non per renderle visibili a chiunque di vedere solo per il bene di test, altrimenti chiunque può li uso da decifrare.Ma sono intrinseco per il codice, complicato, e la necessità di lavorare sempre (l'ovvia eccezione è la riflessione che può essere utilizzato per visualizzare anche i metodi privati nella maggior parte dei casi, quando SecurityManager non è configurato per evitare questo).
  2. La creazione di un SDK per la comunità consumo.Qui pubblico assume un totalmente diverso significato, dal momento che questo è il codice che tutto il mondo può vedere (e non solo interno della mia applicazione).Ho messo il codice in privato metodi, se non mi desidera che l'SDK agli utenti di vedere - mi non vedere questo come codice odore, solo come SDK programmazione delle opere.Ma di ovviamente ho ancora bisogno di testare il mio i metodi privati, e sono dove la funzionalità del mio SDK in realtà vita.

Capisco l'idea di solo testare il "contratto".Ma non vedo un possibile sostenitore in realtà non è il codice di prova - il vostro chilometraggio può variare.

Quindi il mio compromesso comporta complicare il JUnits con la riflessione, piuttosto che compromettere la mia sicurezza e SDK.

I metodi privati sono chiamati da un metodo pubblico, in modo che gli ingressi per il pubblico metodi dovrebbe anche esaminare i metodi privati che sono chiamati da coloro metodi pubblici.Quando un metodo pubblico non riesce, quindi, che potrebbe essere un errore di metodo privato.

Un altro approccio che ho utilizzato è quello di cambiare il metodo privato a pacchetto privato o protetto, quindi si integrano con il @VisibleForTesting annotazione di Google Guava biblioteca.

Questo consentirà di dire a qualcuno che utilizza questo metodo di fare attenzione e di non accedervi direttamente, anche in un pacchetto.Anche una classe di test non ha bisogno di essere nello stesso pacchetto fisicamente, ma nello stesso pacchetto sotto l' prova cartella.

Per esempio, se un metodo per essere testato src/main/java/mypackage/MyClass.java quindi il test di chiamata deve essere posto in src/test/java/mypackage/MyClassTest.java.In questo modo, si ha accesso al metodo di prova nella classe di test.

Per testare il codice legacy con grande e stravagante classi, spesso è molto utile per essere in grado di testare un privato (o pubblico) metodo sto scrivendo adesso.

Io uso il junitx.util.PrivateAccessor-pacchetto Java .Un sacco di utili one-liners per l'accesso privato metodi e campi privati.

import junitx.util.PrivateAccessor;

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

Dopo aver provato Cem Catikkas' soluzione utilizzando la reflection per Java, devo dire che la sua è stata una soluzione più elegante di quello che ho descritto qui.Tuttavia, se siete alla ricerca di un'alternativa all'utilizzo di riflessione, e di avere accesso alla sorgente che si sta testando, questo continua ad essere un'opzione.

Non è possibile, è merito di un test privato i metodi di una classe, in particolare con sviluppo test-driven, dove vuoi disegnare piccolo test prima di scrivere alcun codice.

La creazione di un test di accesso ai membri privati e metodi di analisi del codice, che sono difficili da colpire specificamente con accesso solo per i metodi pubblici.Se un metodo ha diversi passaggi coinvolti, si può comprendere diversi metodi privati, che possono poi essere analizzati singolarmente.

Vantaggi:

  • Possibile eseguire un test per una maggiore granularità

Svantaggi:

  • Codice di Test deve risiedere nella stessa file di codice sorgente, che può essere più difficile da mantenere
  • Allo stesso modo con .classe file di output, devono rimanere all'interno dello stesso pacchetto, come dichiarato nel codice sorgente

Tuttavia, se continui test richiede questo metodo, può essere un segnale che il privato metodi devono essere estratti, che potrebbe essere testato nel tradizionale modo pubblico.

Qui è un contorto esempio di come questo dovrebbe funzionare:

// 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 vorresti essere compilato per ClassToTest$StaticInnerTest.

Vedere anche: Java Punta 106:Statico classi interne per divertimento e profitto

Come hanno detto gli altri...non testare i metodi privati direttamente.Qui sono alcuni pensieri:

  1. Tenere tutti i metodi piccolo e concentrato (facile da testare, semplice per trovare ciò che è sbagliato)
  2. Utilizzare strumenti di copertura del codice.Mi piace Cobertura (oh happy day, si presenta come una nuova versione!)

Eseguire il codice di copertura al test di unità.Se vedi che i metodi non sono completamente testati aggiungere alle prove per ottenere la copertura.Obiettivo per il 100% di copertura del codice, ma si rende conto che probabilmente non ottenere.

Metodi privati sono consumati da quelli pubblici.In caso contrario, sono morti codice.Ecco perché si verifica il metodo pubblico, affermando che i risultati attesi del metodo pubblico e, di conseguenza, i metodi privati che consuma.

Test metodi privati dovrebbero essere testati con il debug prima esecuzione di unit test su metodi pubblici.

Essi possono anche eseguire il debug utilizzando il test-driven development, debug unit test fino a quando tutte le vostre affermazioni sono soddisfatte.

Personalmente credo sia meglio creare classi utilizzando TDD;creare il metodo pubblico stub, quindi generating unit test con tutti le affermazioni definite in anticipo, in modo che il risultato atteso del metodo è determinata prima di codice.In questo modo, non andare giù per il sentiero sbagliato di fare il test di unità affermazioni montare i risultati.La tua classe è quindi robusto e soddisfa i requisiti quando tutte le vostre unità di test pass.

Nel Framework Spring è possibile verificare i metodi privati che utilizza questo metodo:

ReflectionTestUtils.invokeMethod()

Per esempio:

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

Se si utilizza la Primavera, ReflectionTestUtils fornisce alcuni strumenti utili che aiutano a qui, con il minimo sforzo.Per esempio, per impostare un finto su un membro privato, senza essere costretti ad aggiungere un indesiderabile pubblico setter:

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

Se stai cercando di testare il codice esistente che siete riluttanti o incapaci di cambiare, di riflessione, è una buona scelta.

Se la classe del design è ancora flessibile, e hai un complicato metodo privato che desideri per testare separatamente, vi suggerisco di tirare fuori in una classe separata, e la prova che la classe separatamente.Questo non deve modificare l'interfaccia pubblica della classe originale;può internamente creare un'istanza della classe di supporto e chiamare il metodo di supporto.

Se volete testare le difficili condizioni di errore proveniente dal metodo di supporto, si può fare un passo ulteriore.Estratto di un'interfaccia dalla classe di supporto, aggiungere un pubblico getter e setter per la classe originale per iniettare la classe di supporto (utilizzabile tramite la sua interfaccia), e quindi di inserire una versione finto di classe helper in originale classe a prova di come l'originale risponde classe di eccezioni da figurante.Questo approccio è utile anche se si desidera testare la classe originale senza testando anche la classe di supporto.

Test metodi privati interrompe l'incapsulamento della classe, perché ogni volta che si cambia l'implementazione interna è di rompere il codice del client (in questo caso, il test).

Quindi non prova i metodi privati.

La risposta JUnit.org la pagina delle FAQ:

Ma se è necessario...

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

Se si utilizza il JDK 1.6 o superiore e annotare le vostre prove con @Test, è possibile utilizzare Dp4j per iniettare riflessione nei vostri metodi di prova.Per dettagli su come usarlo, vedi questo script di test.

P. S.Io sono il principale collaboratore di Dp4j, chiedere mi se hai bisogno di aiuto.:)

Se si desidera testare i metodi privati di un'applicazione legacy, in cui non è possibile modificare il codice, una opzione per Java è jMockit, che vi permetterà di creare simulazioni di un oggetto anche quando sono privati della classe.

Io tendo a non testare i metodi privati.C'è follia.Personalmente, credo che si debba solo prova la tua interfacce esposte pubblicamente (e che include protetto e metodi interni).

Se si utilizza JUnit, guarda junit-addons.Ha la capacità di ignorare il modello di sicurezza Java e accesso privato metodi e attributi.

Un metodo privato è solo per essere letta all'interno di una stessa classe.Quindi non c'è modo di testare un “privato” metodo di una classe di destinazione da ogni classe di test.Un modo è che si può eseguire unit test manualmente o può cambiare il metodo da “privato” per “protetto”.

E poi un metodo protetto accessibile solo all'interno dello stesso pacchetto in cui la classe è definita.Così, la sperimentazione di un metodo protetto di una classe di destinazione significa che abbiamo bisogno di definire la classe di test nello stesso pacchetto, come la classe di destinazione.

Se tutto quanto sopra non si adatta alle vostre esigenze, utilizzare la riflessione modo per accedere al metodo privato.

Vorrei suggerire di refactoring di codice un po'.Quando si deve iniziare a pensare all'utilizzo di riflessione o altre cose, solo per il test del codice, qualcosa è andato storto con il vostro codice.

Lei ha accennato a diversi tipi di problemi.Cominciamo con campi privati.Nel caso di campi privati io avrei aggiunto un nuovo costruttore e iniettato campi che.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 codice legacy.Vecchio codice che utilizza un costruttore vuoto, e se mi chiedete, refactoring del codice avrà un aspetto più pulito, e sarete in grado di iniettare valori necessari nei test senza riflessione.

Ora su metodi privati.Nella mia esperienza personale, quando sono stub un metodo privato per il test, quindi, che il metodo non ha nulla a che fare in classe.Un modello comune, in questo caso, sarebbe quello di avvolgere è all'interno di un'interfaccia, come Callable e poi si passa in quel di interfaccia, nel costruttore (con più costruttore di trucco):

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

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

Per lo più tutto quello che ho scritto sembra una iniezione di dipendenza modello.Nella mia esperienza personale è molto utile durante il test, e penso che questo tipo di codice è più pulito e più facile da mantenere.Direi la stessa cosa di classi nidificate.Se una classe nidificata contiene logica, sarebbe meglio se si sarebbe spostata come un pacchetto di lezioni private e hanno iniettato in una classe che ne hanno bisogno.

Ci sono anche diversi altri modelli di progettazione che ho usato durante il refactoring e il mantenimento di codice legacy, ma tutto dipende dai casi di codice di test.Utilizzo di riflessione per lo più non è un problema, ma quando si dispone di un'applicazione aziendale, che è ampiamente testate e i test vengono eseguiti prima di ogni distribuzione, tutto diventa molto lento (è solo fastidioso e non mi piace questo genere di cose).

C'è anche un setter, ma non mi ha consigliato di utilizzare esso.Avrei fatto meglio a bastone con un costruttore e inizializzare tutto quando è davvero necessario, lasciando la possibilità per l'iniezione di dipendenze necessarie.

Come molti sopra hanno suggerito, un buon modo è quello di testare le loro tramite il tuo interfacce pubbliche.

Se si esegue questa operazione, è una buona idea per l'utilizzo di un strumento di copertura del codice (come Emma) per vedere se i metodi privati sono in realtà eseguiti dalle vostre prove.

Qui è il mio generica funzione di test privati campi:

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);
}

Si prega di vedere sotto per un esempio;

La seguente istruzione import dovrebbero essere aggiunti:

import org.powermock.reflect.Whitebox;

Ora si può passare direttamente l'oggetto che ha il metodo privato, il nome del metodo da chiamare, e di altri parametri come riportato di seguito.

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

Oggi, ho spinto una libreria Java per aiutare il test privati, metodi e campi.Esso è stato progettato con Android in mente, ma si può davvero essere utilizzato per qualsiasi progetto Java.

Se hai un po ' di codice con i metodi privati o dei campi e costruttori, è possibile utilizzare BoundBox.Fa esattamente quello che stai cercando.Qui di seguito è un esempio di un test che accede a due campi privati di Android per attività di test:

@UiThreadTest
public void testCompute() {

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

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

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

BoundBox lo rende facile per testare private/protected campi, metodi e costruttori.È anche possibile accedere roba che è nascosto in eredità.Infatti, BoundBox interruzioni di incapsulamento.Essa vi darà accesso a tutto ciò che, attraverso la riflessione, MA tutto è controllato in fase di compilazione.

È ideale per il test di codice legacy.Da usare con cautela.;)

https://github.com/stephanenicolas/boundbox

Primo, io lancio questa domanda:Perché il privato è necessario che i fedeli isolato test?Sono complessi, fornendo così complessi comportamenti da richiedere un test a parte il pubblico di superficie?E ' unit test, non e 'la linea di codice di test.Non sudare le piccole cose.

Se sono grande, abbastanza grande che questi soci privati sono ciascuna una 'unità' grande complessità-considerare di refactoring privata dei membri di questa classe.

Se il refactoring è inadeguato o non fattibile, è possibile utilizzare il modello di strategia per sostituire l'accesso a queste membro privato di funzioni di membro classi di sotto di test di unità?Al di sotto di test di unità, la strategia di fornire ulteriore convalida, ma nella versione build sarebbe semplice passthrough.

Recentemente ho avuto questo problema e ha scritto un piccolo strumento, chiamato Scassinare, che consente di evitare i problemi in modo esplicito utilizzando il Java API reflection, due esempi:

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

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

La chiamata di metodi, ad es. private void method(String s) - da Scassinare

interface Accessible {
  void method(String s);
}

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

Campi, ad es. private BigInteger amount; - Java riflessione

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

Campi, ad es. private BigInteger amount; - da Scassinare

interface Accessible {
  void setAmount(BigInteger amount);
}

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

Per Java che mi piacerebbe utilizzare riflessione, siccome non mi piace l'idea di cambiare l'accesso al pacchetto dichiarato il metodo giusto per il gusto di test.Tuttavia, di solito mi limito a testare i metodi pubblici che dovrebbero garantire i metodi privati stanno lavorando correttamente.

non è possibile utilizzare la reflection per ottenere metodi privati da fuori classe proprietario, il modificatore private colpisce riflessione anche

Questo non è vero.È certamente possibile, come detto in Cem Catikkas risposta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top