Come testate i metodi privati?


184

Sto lavorando a un progetto Java. Sono nuovo ai test unitari. Qual è il modo migliore per testare unitamente metodi privati ​​nelle classi java?


1
Controlla questa domanda su StackOverflow. Un paio di tecniche sono menzionate e discusse. Qual è il modo migliore di testare metodi privati?
Chirone

34
La mia opinione è sempre stata che i metodi privati ​​non necessitano di test in quanto dovresti testare ciò che è disponibile. Un metodo pubblico. Se non riesci a violare il metodo pubblico, importa davvero cosa stanno facendo i metodi privati?
Rig

1
Entrambi i metodi pubblici e privati ​​dovrebbero essere testati. Quindi, un driver di prova deve generalmente essere all'interno della classe che verifica. come questo .
scambio eccessivo del

2
@Rig - +1 - dovresti essere in grado di invocare tutto il comportamento richiesto di un metodo privato dai tuoi metodi pubblici - se non puoi, la funzionalità non può mai essere invocata, quindi non ha senso testarlo.
James Anderson,

Utilizzare @Jailbreakdal framework Manifold per accedere direttamente ai metodi privati. In questo modo il codice di test rimane sicuro e leggibile. Soprattutto, nessun compromesso di progettazione, nessun metodo e campi sovraesposti per il bene dei test.
Scott

Risposte:


239

Generalmente non testate direttamente i metodi privati. Dal momento che sono privati, considerali un dettaglio di implementazione. Nessuno lo chiamerà mai e si aspetta che funzioni in un modo particolare.

Dovresti invece testare la tua interfaccia pubblica. Se i metodi che chiamano i tuoi metodi privati ​​funzionano come previsto, supponi per estensione che i tuoi metodi privati ​​funzionino correttamente.


39
+1 bazillion. E se non viene mai chiamato un metodo privato, non testarlo, eliminalo!
Steven A. Lowe,

246
Non sono d'accordo. A volte un metodo privato è solo un dettaglio dell'implementazione, ma è ancora abbastanza complesso da giustificare il test, per assicurarsi che funzioni correttamente. L'interfaccia pubblica potrebbe offrire un livello di astrazione troppo elevato per scrivere un test che abbia come obiettivo diretto questo particolare algoritmo. Non è sempre possibile fattorizzarlo in una classe separata, a causa dei dati condivisi. In questo caso direi che va bene testare un metodo privato.
quant_dev,

10
Ovviamente cancellalo. L'unico motivo possibile per non cancellare una funzione che non stai usando è se pensi di poterne avere bisogno in futuro, e anche allora dovresti eliminarla ma annota la funzione nei tuoi log di commit, o almeno commentala in modo che la gente sa che è qualcosa in più che può ignorare.
jhocking

38
@quant_dev: a volte una classe deve essere rifattorizzata in diverse altre classi. I tuoi dati condivisi possono anche essere inseriti in una classe separata. Diciamo, una classe "contestuale". Quindi le tue due nuove classi possono fare riferimento alla classe di contesto per i loro dati condivisi. Questo può sembrare irragionevole, ma se i tuoi metodi privati ​​sono abbastanza complessi da richiedere test individuali, è un odore di codice che indica che il tuo grafico a oggetti deve diventare un po 'più granulare.
Phil

43
Questa risposta NON risponde alla domanda. La domanda è come dovrebbero essere testati i metodi privati, non se debbano essere testati. Se un metodo privato debba essere testato unitamente è una domanda interessante e degna di discussione, ma non qui . La risposta appropriata è aggiungere un commento alla domanda affermando che testare metodi privati ​​potrebbe non essere una buona idea e fornire un collegamento a una domanda separata che approfondirebbe la questione.
TallGuy

118

In generale, lo eviterei. Se il tuo metodo privato è così complesso da richiedere un test unitario separato, spesso significa che meritava una sua classe. Questo può incoraggiarti a scriverlo in modo riutilizzabile. Dovresti quindi testare la nuova classe e chiamarne l'interfaccia pubblica nella tua vecchia classe.

D'altra parte, a volte il factoring dei dettagli dell'implementazione in classi separate porta a classi con interfacce complesse, un sacco di dati che passano tra la vecchia e la nuova classe, o a un design che può sembrare buono dal punto di vista OOP, ma non abbinare le intuizioni che provengono dal dominio problematico (ad esempio, dividere un modello di prezzi in due parti solo per evitare di testare metodi privati ​​non è molto intuitivo e può portare a problemi in seguito durante la manutenzione / estensione del codice). Non vuoi avere "classi gemelle" che vengono sempre cambiate insieme.

Di fronte a una scelta tra incapsulamento e testabilità, preferirei scegliere il secondo. È più importante avere il codice corretto (cioè produrre l'output corretto) di un bel design OOP che non funziona correttamente, perché non è stato testato adeguatamente. In Java, puoi semplicemente dare al metodo l'accesso "predefinito" e mettere il test unitario nello stesso pacchetto. I test unitari fanno semplicemente parte del pacchetto che si sta sviluppando ed è OK avere una dipendenza tra i test e il codice che viene testato. Ciò significa che quando si modifica l'implementazione, potrebbe essere necessario modificare i test, ma va bene così: ogni modifica dell'implementazione richiede un nuovo test del codice e se i test devono essere modificati per farlo, allora basta esso.

In generale, una classe può offrire più di un'interfaccia. C'è un'interfaccia per gli utenti e un'interfaccia per i manutentori. Il secondo può esporre di più per garantire che il codice sia adeguatamente testato. Non deve essere un test unitario su un metodo privato, ad esempio potrebbe essere la registrazione. Anche la registrazione "rompe l'incapsulamento", ma lo facciamo ancora, perché è molto utile.


9
+1 Punto interessante. Alla fine si tratta di manutenibilità, non di aderenza alle "regole" del buon OOP.
Phil

9
+1 Sono completamente d'accordo con la testabilità sull'incapsulamento.
Richard,

31

Il collaudo di metodi privati ​​dipenderebbe dalla loro complessità; alcuni metodi privati ​​a una riga non meriterebbero davvero lo sforzo extra dei test (questo si può dire anche dei metodi pubblici), ma alcuni metodi privati ​​possono essere tanto complessi quanto i metodi pubblici e difficili da testare attraverso l'interfaccia pubblica.

La mia tecnica preferita è quella di rendere privato il pacchetto del metodo privato, che consentirà l'accesso a un unit test nello stesso pacchetto ma sarà comunque incapsulato da tutto il resto del codice. Ciò offrirà il vantaggio di testare direttamente la logica del metodo privato invece di dover fare affidamento su un test del metodo pubblico per coprire tutte le parti della (possibilmente) logica complessa.

Se questo è associato all'annotazione @VisibleForTesting nella libreria di Google Guava, stai chiaramente contrassegnando questo metodo privato del pacchetto come visibile solo per il test e come tale, non dovrebbe essere chiamato da altre classi.

Gli oppositori di questa tecnica sostengono che ciò spezzerà l'incapsulamento e aprirà metodi privati ​​per codificare nello stesso pacchetto. Anche se concordo sul fatto che questo rompe l'incapsulamento e apre il codice privato ad altre classi, sostengo che testare una logica complessa è più importante dell'incapsulamento rigoroso e non usare metodi privati ​​del pacchetto che sono chiaramente contrassegnati come visibili solo per il test deve essere la responsabilità degli sviluppatori utilizzando e modificando la base di codice.

Metodo privato prima del test:

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

Metodo privato del pacchetto pronto per il test:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Nota: inserire i test nello stesso pacchetto non equivale a metterli nella stessa cartella fisica. Separare il codice principale e il codice di test in strutture di cartelle fisiche separate è una buona pratica in generale, ma questa tecnica funzionerà fintanto che le classi sono definite come nello stesso pacchetto.


14

Se non è possibile utilizzare API esterne o non si desidera, è comunque possibile utilizzare l'API JDK standard pura per accedere a metodi privati ​​mediante la riflessione. Ecco un esempio

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Controlla il tutorial Java http://docs.oracle.com/javase/tutorial/reflect/ o l'API Java http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html per ulteriori informazioni.

Come citato da @kij nella sua risposta, ci sono momenti in cui una soluzione semplice che utilizza la riflessione è davvero buona per testare un metodo privato.


9

Caso di test unitario significa testare l'unità di codice. Non significa testare l'interfaccia perché se stai testando l'interfaccia, ciò non significa che stai testando l'unità di codice. Diventa una specie di test in scatola nera. Inoltre, è meglio trovare i problemi a livello di unità più piccolo piuttosto che determinare i problemi a livello di interfaccia e quindi provare a eseguire il debug di quel pezzo che non funzionava. Pertanto, il caso di test unitario deve essere testato indipendentemente dal loro ambito di applicazione. Di seguito è riportato un modo per testare metodi privati.

Se stai usando java, puoi usare jmockit che fornisce Deencapsulation.invoke per chiamare qualsiasi metodo privato della classe sotto test. Usa la riflessione per chiamarlo alla fine, ma offre un bel involucro attorno ad esso. ( https://code.google.com/p/jmockit/ )


come risponde alla domanda posta?
moscerino il

@gnat, La domanda era: qual è il modo migliore per testare unitamente i metodi privati ​​nelle classi java? E il mio primo punto risponde alla domanda.
agrawalankur,

il tuo primo punto si limita a pubblicizzare uno strumento, ma non spiega perché ritieni che sia migliore delle alternative, come ad esempio testare metodi privati ​​indirettamente, come è stato suggerito e spiegato nella risposta migliore
moscerino

jmockit si è spostato e la vecchia pagina ufficiale indica un articolo su "Urina sintetica e pipì artificiale". A proposito, è ancora legato a cose beffardo, ma non pertinente qui :) La nuova pagina ufficiale è: jmockit.github.io
Guillaume

8

Prima di tutto, come suggerito da altri autori: pensaci due volte se hai davvero bisogno di testare il metodo privato. E se così fosse, ...

In .NET è possibile convertirlo in metodo "Interno" e rendere il pacchetto " InternalVisible " nel progetto di unit test.

In Java puoi scrivere i test stessi nella classe da testare e anche i tuoi metodi di test dovrebbero essere in grado di chiamare metodi privati. Non ho davvero una grande esperienza Java, quindi probabilmente non è la migliore pratica.

Grazie.


7

Di solito faccio tali metodi protetti. Diciamo che la tua lezione è in:

src/main/java/you/Class.java

È possibile creare una classe di test come:

src/test/java/you/TestClass.java

Ora hai accesso ai metodi protetti e puoi testarli in unità (JUnit o TestNG non contano davvero), ma conservi questi metodi dai chiamanti che non volevi.

Si noti che ciò prevede un albero dei sorgenti in stile maven.


3

Se hai davvero bisogno di testare un metodo privato, con Java intendo, puoi usare fest asserte / o fest reflect. Usa la riflessione.

Importa la libreria con Maven (le versioni fornite non sono le ultime credo) o importale direttamente nel tuo percorso di classe:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Ad esempio, se hai una classe denominata "MyClass" con un metodo privato denominato "myPrivateMethod" che accetta una stringa come parametro e aggiorna il suo valore a "questo è un test interessante!", Puoi eseguire il seguente test junit:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Questa libreria consente anche di sostituire qualsiasi proprietà del bean (indipendentemente dal fatto che siano private e che nessun setter sia scritto) da un mock, e usarlo con Mockito o qualsiasi altro framework mock è davvero bello. L'unica cosa che devi sapere al momento (non so se sarà meglio nelle prossime versioni) è il nome del campo / metodo target che vuoi manipolare e la sua firma.


2
Mentre reflection ti consentirà di testare metodi privati, non è utile per rilevarne gli usi. Se cambi il nome di un metodo privato, questo interromperà il test.
Richard,

1
Il test si interromperà sì, ma questo è un problema minore dal mio punto di vista. Naturalmente sono pienamente d'accordo sulla tua spiegazione di seguito sulla visibilità del pacchetto (con classi di test in cartelle separate ma con gli stessi pacchetti), ma a volte non lo fai ho davvero la scelta. Ad esempio, se si ha una missione molto breve in un'azienda che non applica metodi "buoni" (classi di test non nello stesso pacchetto, ecc.), Se non si ha il tempo di rifattorizzare il codice esistente su cui si sta lavorando / testing, questa è ancora una buona alternativa.
kij,

2

Quello che faccio normalmente in C # è rendere i miei metodi protetti e non privati. È un modificatore di accesso privato leggermente meno, ma nasconde il metodo da tutte le classi che non ereditano dalla classe sottoposta a test.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Qualsiasi classe che non eredita direttamente da classUnderTest non ha idea che esiste anche methodToTest. Nel mio codice di test, posso creare una classe di test speciale che si estende e fornisce l'accesso a questo metodo ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Questa classe esiste solo nel mio progetto di test. Il suo unico scopo è fornire l'accesso a questo metodo unico. Mi permette di accedere a luoghi che la maggior parte delle altre classi non ha.


4
protectedfa parte dell'API pubblica e presenta le stesse limitazioni (non può mai essere modificato, deve essere documentato, ...). In C # puoi usare internalinsieme a InternalsVisibleTo.
CodesInChaos,

1

Puoi testare facilmente metodi privati ​​se metti i test unitari in una classe interna sulla classe che stai testando. Utilizzando TestNG i test unitari devono essere classi interne statiche pubbliche annotate con @Test, in questo modo:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Poiché è una classe interna, è possibile chiamare il metodo privato.

I miei test sono eseguiti da Maven e trova automaticamente questi casi di test. Se vuoi solo provare una classe, puoi farlo

$ mvn test -Dtest=*UserEditorTest

Fonte: http://www.ninthavenue.com.au/how-to-unit-test-private-methods


4
Stai suggerendo di includere il codice di test (e le classi interne aggiuntive) nel codice effettivo del pacchetto distribuito?

1
La classe test è una classe interna della classe che si desidera verificare. Se non si desidera distribuire quelle classi interne, è sufficiente eliminare i file * Test.class dal pacchetto.
Roger Keays l'

1
Non amo questa opzione, ma per molti è probabilmente una soluzione valida, non degna dei voti negativi.
Bill K,

@RogerKeays: tecnicamente quando si esegue una distribuzione, come si rimuovono tali "classi interne di test"? Per favore, non dire che li hai tagliati a mano.
Gyorgyabraham,

Non li rimuovo. Sì. Distribuisco codice che non viene mai eseguito in fase di esecuzione. È davvero così male.
Roger Keays,
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.