Questo articolo confronta quattro diversi approcci alla sperimentazione di metodi privati di classi Java.
Il mio primo utilizzo di JUnit è stato quello di costruire un kit di test di conformità per l’API ServiceUI . Lo scopo di un kit di test di conformità è quello di garantire che implementazioni alternative della stessa API siano compatibili con le specifiche dell’API. Poiché una specifica API definisce solo l’interfaccia pubblica dell’API, non l’implementazione dell’API, un test di conformità esercita solo l’interfaccia pubblica. In altre parole, un test di conformità è un test “scatola nera”. Tratta l’API in prova come una scatola nera, la cui interfaccia esterna può essere vista, ma la cui implementazione interna non può. Un test di conformità di un’API Java, quindi, deve accedere solo ai membri pubblici dei pacchetti e delle classi in prova. Non è necessario accedere a membri a livello di pacchetto, protetti o privati.
Quando in seguito ho applicato JUnit al compito di scrivere test unitari effettivi, al contrario dei test di conformità, mi sono trovato a voler scrivere test white box—test che impiegano la conoscenza dell’implementazione interna dei pacchetti e delle classi in prova. Mentre volevo solo testare metodi pubblici nei miei test di conformità, volevo scrivere test unitari per l’accesso ai pacchetti e occasionalmente metodi privati e metodi pubblici.
Daniel Steinberg mi ha mostrato la comune tecnica JUnit di usare alberi di codice sorgente paralleli, che mi ha permesso di posizionare le classi di test nello stesso pacchetto delle classi in prova, ma tenerle in una directory diversa. Ciò ha fornito una separazione pulita del codice di prova e di produzione. Posizionando entrambi gli alberi di origine nel CLASSPATH, le mie classi di test potrebbero accedere ai metodi e alle classi a livello di pacchetto nel pacchetto in prova. Questo mi ha lasciato ancora, tuttavia, con il problema di testare metodi privati.
Quando ho chiesto a Daniel di testare i metodi privati, ha gentilmente suggerito di testare i metodi privati indirettamente testando l’accesso ai pacchetti e i metodi pubblici che chiamano quelli privati. Questa risposta non mi ha del tutto soddisfatto, perché a volte ho davvero sentito l’impulso di testare direttamente un metodo privato. La mia soluzione iniziale era quella di rendere tale accesso al pacchetto di metodi privati, che mi permetteva di testarli direttamente con JUnit dalle classi di test nello stesso pacchetto nell’albero dei sorgenti parallelo. Questo ha funzionato bene, ma mi ha fatto sentire un po ‘ sporco in qualche modo. Anche se in generale ho scoperto che pensare a come progettare le interfacce in modo che possano essere facilmente testate in unità mi ha aiutato a progettare interfacce migliori, in questo caso ho sentito che stavo rendendo il design leggermente peggiore per renderlo testabile.
Quando in seguito ho finito per partecipare alla creazione di ciò che Frank Sommers, Matt Gerrans e alla fine ho pubblicato come Artima SuiteRunner, ho promesso che avrei reso più facile il test dei metodi privati in SuiteRunner che in JUnit. Ma dopo aver studiato i vari approcci per testare i metodi privati, ho deciso di non fare nulla di speciale in SuiteRunner per supportare il test dei metodi privati. Quindi, se stai usando JUnit o SuiteRunner, hai gli stessi quattro approcci di base per testare i metodi privati:
- Non testare metodi privati.
- Consente l’accesso al pacchetto methods.
- Usa una classe di test nidificata.
- Usa riflessione.
In questo articolo, discuterò questi quattro approcci per testare metodi privati in Java. Esaminerò i vantaggi e gli svantaggi di ciascuno e tenterò di far luce su quando ha senso utilizzare ciascun approccio.
Come ho detto nell’introduzione, ho sentito per la prima volta il consiglio di sopprimere i miei impulsi occasionali di testare metodi privati da Daniel Steinberg. Ma Daniel non è solo fonte di questo consiglio che ho incontrato. Sembra essere un atteggiamento comune nella comunità Java. Ad esempio, la FAQ JUnit afferma:
Il test dei metodi privati può essere un’indicazione che tali metodi dovrebbero essere spostati in un’altra classe per promuovere la riusabilità.
Charles Miller ha espresso un punto di vista simile nel suo blog :
Se si dispone di una suite completa di test per l’interfaccia esposta (non privata) di una classe, tali test dovrebbero, per loro natura, verificare che anche qualsiasi metodo privato all’interno della classe funzioni. Se questo non è il caso, o se si dispone di un metodo privato così complesso che deve essere testato fuori dal contesto dei suoi chiamanti pubblici, lo considererei un odore di codice.
E Dave Thomas e Andy Hunt , nel loro libro Pragmatic Unit Testing, scrivono:
In generale, non vuoi rompere alcun incapsulamento per motivi di test (o come diceva mamma, ” non esporre i tuoi privati!”). La maggior parte delle volte, dovresti essere in grado di testare una classe esercitando i suoi metodi pubblici. Se c’è una funzionalità significativa nascosta dietro l’accesso privato o protetto, questo potrebbe essere un segnale di avvertimento che c’è un’altra classe in là che lotta per uscire.
Credo che tutto questo consiglio. La maggior parte delle volte, i metodi privati possono essere testati in modo più efficace tramite l’approccio 1, testando indirettamente i metodi a livello di pacchetto, protetti e pubblici che li chiamano. Ma inevitabilmente, alcune persone in alcune situazioni sentiranno che testare direttamente un metodo privato è la cosa giusta da fare.
Nel mio caso, tendo a creare molti metodi di utilità privata. Questi metodi di utilità spesso non fanno nulla con i dati dell’istanza, operano solo sui parametri passati e restituiscono un risultato. Creo tali metodi per rendere il metodo di chiamata più facile da capire. È un modo per gestire la complessità dell’implementazione della classe. Ora, se estraggo il metodo privato da un metodo che funziona già e ha una buona copertura per i test unitari, allora i test unitari esistenti saranno probabilmente sufficienti. Non ho bisogno di scrivere più test unitari solo per il metodo privato. Ma se voglio scrivere il metodo privato prima del suo metodo di chiamata, e voglio scrivere i test unitari prima di scrivere il metodo privato, sono tornato a voler testare direttamente il metodo privato. Nel caso dei metodi di utilità privata, non sento il mio bisogno di testare direttamente i metodi, come dice la FAQ JUnit, “un’indicazione che quei metodi dovrebbero essere spostati in un’altra classe per promuovere la riusabilità.”Questi metodi sono davvero necessari solo nella classe in cui risiedono, e in effetti sono spesso chiamati solo da un altro metodo.
Un altro motivo per cui a volte sento l’impulso di testare direttamente i metodi privati è che tendo a pensare ai test unitari come ad aiutarmi a ottenere un sistema robusto costruendo quel sistema da parti robuste. Ogni parte è una ” unità “per la quale posso scrivere” test unitari.”I test unitari mi aiutano a garantire che ogni unità funzioni correttamente, il che a sua volta mi aiuta a costruire un sistema che funzioni correttamente nel suo complesso. L’unità primaria che penso in termini di programmazione in Java è la classe. Costruisco sistemi fuori dalle classi e i test unitari mi danno la certezza che le mie classi sono robuste. Ma in una certa misura mi sento allo stesso modo anche riguardo ai metodi privati di cui compongo i metodi package-access, protected e public. Questi metodi privati sono unità che possono essere testate individualmente. Tali test unitari mi danno la certezza che i metodi privati funzionino correttamente, il che mi aiuta a creare metodi di accesso ai pacchetti, protetti e pubblici robusti.
Come ho detto nell’introduzione, dare accesso ai pacchetti methods è stato il mio primo approccio al test dei metodi privati con JUnit. Questo approccio funziona davvero bene, ma ha un leggero costo. Quando vedo un identificatore di accesso privato su un metodo, mi dice qualcosa che mi piace sapere-che questo fa parte dell’implementazione della classe. So che posso ignorare il metodo se sto solo cercando di usare la classe da un’altra classe nel pacchetto. Potrei capire questo su un metodo di accesso ai pacchetti osservando più da vicino il nome, la documentazione e il codice del metodo, ma la parola private comunica in modo molto più efficiente. Inoltre, il problema principale che ho con questo approccio è filosofico. Anche se non mi dispiace “rompere l’incapsulamento per motivi di test”, come direbbero Dave e Andy, non mi sento bene nel rompere l’incapsulamento in un modo che cambia l’API a livello di pacchetto. In altre parole, anche se sono abbastanza entusiasta di testare metodi di classi non pubblici, cioè per creare test unitari “white-box”, preferirei che l’API delle classi in prova, inclusa l’API a livello di pacchetto, non venisse modificata per facilitare tali test.
Approccio 3: utilizzare una classe di test nidificata
Un terzo approccio per testare i metodi privati consiste nel nidificare una classe di test statico all’interno della classe di produzione in fase di test. Dato che una classe nidificata ha accesso ai membri privati della sua classe che racchiude, sarebbe in grado di richiamare direttamente i metodi privati. La classe statica stessa potrebbe essere l’accesso al pacchetto, consentendo di caricarlo come parte del test white box.
Lo svantaggio di questo approccio è che se non vuoi che la classe di test nidificata sia accessibile nel tuo file JAR di distribuzione, devi fare un po ‘ di lavoro extra per estrarlo. Inoltre, ad alcune persone potrebbe non piacere avere il codice di test mescolato nello stesso file del codice di produzione, anche se altri potrebbero preferire questo approccio.
Approccio 4: Usa Reflection
Il quarto approccio al test dei metodi privati mi è stato suggerito da Vladimir R. Bossicard, che ha scritto JUnit Addons . Un giorno durante il pranzo, Vladimir mi ha illuminato che l’API java.lang.reflect
includeva metodi che consentivano al codice client di aggirare il meccanismo di protezione dell’accesso della macchina virtuale Java. Mi ha anche detto che il suo progetto JUnit Addons includeva una classe, junitx.util.PrivateAccessor
, per aiutare a utilizzare l’API reflection proprio per questo scopo: scrivere test unitari che manipolano i membri privati delle classi in prova. Le FAQ JUnit indicano una classe simile, chiamata PrivilegedAccessor
, scritta da Charlie Hubbard e Prashant Dhotke.
Un vantaggio dell’utilizzo dell’approccio reflection per testare i metodi privati è che fornisce una separazione pulita del codice di test e del codice di produzione. I test non devono essere nidificati all’interno della classe in prova, come nell’approccio 3. Piuttosto, possono essere posizionati insieme agli altri test che esercitano i metodi a livello di pacchetto e pubblici della classe. Inoltre, non è necessario modificare l’API della classe in prova. A differenza dell’approccio 2, i metodi privati possono rimanere privati. A differenza dell’approccio 3, non è necessario aggiungere alcuna classe nidificata aggiuntiva a livello di accesso al pacchetto. Lo svantaggio principale di questo approccio è che il codice di test è molto più dettagliato perché utilizza l’API reflection. Inoltre, gli IDE di refactoring come Eclipse e IntelliJ di solito non sono così abili nel cambiare i nomi dei metodi in cui vengono indicati come String
passati ai metodi dell’API reflection. Quindi, se cambi il nome del metodo privato con il tuo IDE di refactoring, potresti comunque dover apportare alcune modifiche manualmente nel codice di test.
Un esempio
Per dare un esempio di un metodo privato che, a mio parere, merita il test diretto delle unità, ho estratto alcune funzionalità dal metodomain
della classeorg.suiterunner.Runner
.Runner.main
analizza gli argomenti della riga di comando ed esegue una suite di test, opzionalmente attivando la GUI. Il metodo che ho estratto,parseArgsIntoLists
, fa parte del lavoro di analisi degli argomenti della riga di comando per l’applicazione SuiteRunner. Ora, per testare il metodo pubblico che chiama questo metodo privato, avrei bisogno di testaremain
. Principale, ovviamente, è l’intera applicazione, il che rende il metodo piuttosto difficile da testare. In effetti, non ho alcun test esistente permain
.
A questo punto, ti starai chiedendo, se stavo scrivendo i test prima nello stile di sviluppo guidato dai test , come ho mai finito per scrivere codice di analisi che non aveva test unitari? La ragione principale è che la mia infezione da test è arrivata in più fasi. In realtà ho preso un’influenza unit test molto prima di aver sentito parlare di JUnit o read Test Infected . Ai tempi in cui stavo costruendo applicazioni Windows in C++, ad esempio, scrivevo un po ‘ di codice per testare un metodo appena implementato, quindi eseguivo quel codice e lo guardavo eseguire passando attraverso il metodo in prova con il debugger. Questo tipo di test unitari mi ha aiutato a raggiungere la robustezza, ma i test stessi non hanno controllato il comportamento corretto. Ho controllato personalmente il comportamento corretto osservando tramite il debugger. I test non sono stati automatizzati e quindi non li ho salvati in modo che potessero essere eseguiti di nuovo in seguito. Quando ho letto Test Infected, ho immediatamente visto il valore di automatizzare i test e tenerli in giro come una sorta di test di regressione dopo il refactoring, ma per molto tempo non aveva senso per me scrivere prima i test. Volevo scrivere i test dopo aver implementato la funzionalità, perché era allora che avevo eseguito i test con il debugger. Un motivo secondario per cui non ho scritto i test prima mentre sviluppavo gran parte di SuiteRunner è che volevo scrivere i test di SuiteRunner con SuiteRunner stesso, nel tentativo di mangiare il mio cibo per cani. Fino a quando l’API di base di SuiteRunner non si è risolta, non avevo il toolkit di test che volevo usare per scrivere i test.
Da quel momento, tuttavia, il virus di test ha preso una presa più forte su di me, e ora preferisco scrivere test unitari prima la maggior parte del tempo. Preferisco scrivere prima i test non tanto perché trovo che finisco con design più puliti, che di solito viene promosso come il principale vantaggio dello sviluppo guidato dai test. Piuttosto, preferisco scrivere prima i test perché trovo che spesso se mi immergo nel codice sotto pressione, con l’intento di scrivere il test più tardi, il test in realtà non viene mai scritto. SuiteRunner stesso ha pochissimi test unitari a questo punto proprio per questo motivo. Ecco il metodo parseArgsIntoLists
:
private static void parseArgsIntoLists(String args, List runpathList, List reportersList, List suitesList) { if (args == null || runpathList == null || reportersList == null || suitesList == null) { throw new NullPointerException(); } for (int i = 0; i < args.length; i++) { if (args.startsWith("-p")) { runpathList.add(args); runpathList.add(args); ++i; } else if (args.startsWith("-g")) { reportersList.add(args); } else if (args.startsWith("-o")) { reportersList.add(args); } else if (args.startsWith("-e")) { reportersList.add(args); } else if (args.startsWith("-f")) { reportersList.add(args); reportersList.add(args); ++i; } else if (args.startsWith("-r")) { reportersList.add(args); reportersList.add(args); ++i; } else if (args.startsWith("-s")) { suitesList.add(args); do { ++i; suitesList.add(args); } while (i + 1 < args.length); } else { throw new IllegalArgumentException("Unrecognized argument: " + args); } } }
La riga di comando per SuiteRunner contiene tre tipi di informazioni utilizzate da SuiteRunner per eseguire i test: runpath, reporters e suite. Il metodo parseArgsIntoLists
passa semplicemente attraverso gli argomenti passati come una matrice di String
s e inserisce ciascun argomento in una delle liste, runpathList
, reportersList
e suitesList
.
Prima di scrivere un test per questo metodo privato, vorrei chiedere se la mia voglia di scrivere questo test unitario rappresenta un odore di codice, come Charles Miller lo ha messo nel suo blog? Indica che parseArgsIntoLists
dovrebbe essere spostato in un’altra classe per promuovere la riusabilità, come suggerisce la FAQ JUnit? Dave e Andy direbbero che è un segnale di avvertimento che c’è un’altra classe lì che lotta per uscire? Beh, forse. Potrei creare in modo consapevole una classe ArgumentsParser
che contiene solo alcuni metodi statici che eseguono il lavoro di analisi. Sia la classe ArgumentsParser
che i metodi che contiene potrebbero essere l’accesso ai pacchetti, il che li renderebbe facili da testare. Ma non mi sembra giusto. Questi metodi sono chiamati solo da Runner.main
. Si sentono chiaramente come metodi privati per me. L’unica ragione per cui li sposterei in una classe ArgumentsParser
è essere in grado di testarli. In effetti userei l’approccio numero 2: rendere l’accesso al pacchetto metodi privati.
Invece, per questo esempio ho deciso di prendere l’approccio numero 4 e usare reflection. Ho guardato sia Vladimir Bossicard junitx.utils.PrivateAccessor
che Charlie Hubbard e Prashant Dhotke PrivilegedAccessor
, ma ho deciso che nessuno di loro mi ha aiutato nel modo in cui volevo. Per prima cosa, entrambe queste classi hanno la capacità di testare i campi per assicurarsi che siano impostati correttamente. Finora non ho mai sentito alcun bisogno di accedere direttamente ai campi privati dai test unitari. Voglio solo essere in grado di testare i metodi di utilità privata. Il problema principale che ho avuto con queste due classi, tuttavia, è il modo in cui hanno affrontato le eccezioni che possono essere lanciate quando si tenta di invocare il metodo privato tramite reflection. Ogni classe ha uno o più metodi il cui lavoro è richiamare un metodo con riflessione. I due metodi invokeMethod
di PrivilegedAccessor
restituiscono qualsiasi eccezione al chiamante, incluse tre eccezioni controllate dichiarate nella clausola throws: NoSuchMethodException
, IllegalAccessException
e InvocationTargetException
. Al contrario, i due metodi invoke
di PrivateAccessor
catturano InvocationTargetException
ed estraggono e lanciano l’eccezione di destinazione, l’eccezione effettiva generata dal metodo invocato. Quindi cattura qualsiasi altra eccezione e lancia NoSuchMethodException
. Non mi piaceva che il chiamante di PrivilegedAccessor.invokeMethod
avrebbe sempre bisogno di gestire le tre eccezioni controllate, perché ho pensato che il modo generale per gestire qualsiasi eccezione sarebbe quello di far fallire il test. Ero anche preoccupato che PrivateAccessor.invoke
stesse gettando via informazioni di traccia dello stack potenzialmente utili nella sua politica di gestione delle eccezioni. Quello che volevo davvero era un metodo che tentava di invocare un metodo privato con reflection, che avvolgeva qualsiasi eccezione generata oltre a InvocationTargetException
in un TestFailedException
non controllato. La maggior parte delle volte questa eccezione causerebbe il fallimento del test. Nei test che si aspettavano che venisse generata un’eccezione, l’eccezione contenuta in InvocationTargetException
poteva essere estratta e testata per la correttezza.
Pertanto, ho scritto invokeStaticMethod
. La chiamata setAccessible(true)
è ciò che consente di richiamare il metodo privato dall’esterno della classe. Un’implementazione invokeStaticMethod
corrispondente da utilizzare con JUnit genererebbe AssertionFailedError
anziché TestFailedException
. Ecco il codice:
private static void invokeStaticMethod(Class targetClass, String methodName, Class argClasses, Object argObjects) throws InvocationTargetException { try { Method method = targetClass.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); method.invoke(null, argObjects); } catch (NoSuchMethodException e) { // Should happen only rarely, because most times the // specified method should exist. If it does happen, just let // the test fail so the programmer can fix the problem. throw new TestFailedException(e); } catch (SecurityException e) { // Should happen only rarely, because the setAccessible(true) // should be allowed in when running unit tests. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new TestFailedException(e); } catch (IllegalAccessException e) { // Should never happen, because setting accessible flag to // true. If setting accessible fails, should throw a security // exception at that point and never get to the invoke. But // just in case, wrap it in a TestFailedException and let a // human figure it out. throw new TestFailedException(e); } catch (IllegalArgumentException e) { // Should happen only rarely, because usually the right // number and types of arguments will be passed. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new TestFailedException(e); } }
Successivamente, ho creato un metodo di convenienza che richiama il particolare metodo privato che volevo testare:
private static void invokeParseArgsIntoLists(String args, List runpathList, List reportersList, List suitesList) throws InvocationTargetException { // Purposely pass null values to the method, to make sure it throws // NullPointerException Class argClasses = {String.class, List.class, List.class, List.class }; Object argObjects = {args, runpathList, reportersList, suitesList }; invokeStaticMethod(Runner.class, "parseArgsIntoLists", argClasses, argObjects); }
Alla fine, potrei scrivere test contro il metodo privato senza troppi ingombri in eccesso, come questo:
public void testParseArgsIntoLists() throws InvocationTargetException { String args = new String; List runpathList = new ArrayList(); List reportersList = new ArrayList(); List suitesList = new ArrayList(); try { invokeParseArgsIntoLists(null, runpathList, reportersList, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, null, reportersList, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, runpathList, null, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, runpathList, reportersList, null); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } args = new String; args = "-p"; args = "\"mydir\""; args = "-g"; args = "-f"; args = "test.out"; args = "-s"; args = "MySuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 3); verify(reportersList.get(0).equals(args)); verify(reportersList.get(1).equals(args)); verify(reportersList.get(2).equals(args)); verify(suitesList.size() == 2); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); args = new String; args = "-p"; args = "\"mydir\""; args = "-e"; args = "-o"; args = "-r"; args = "MyCustomReporter"; args = "-s"; args = "MySuite"; args = "MyOtherSuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 4); verify(reportersList.get(0).equals(args)); verify(reportersList.get(1).equals(args)); verify(reportersList.get(2).equals(args)); verify(reportersList.get(3).equals(args)); verify(suitesList.size() == 3); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); verify(suitesList.get(2).equals(args)); args = new String; args = "-p"; args = "\"serviceuitest-1.1beta4.jar myjini http://myhost:9998/myfile.jar\""; args = "-g"; args = "-s"; args = "MySuite"; args = "MySecondSuite"; args = "MyThirdSuite"; args = "MyFourthSuite"; args = "MyFifthSuite"; args = "MySixthSuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 1); verify(reportersList.get(0).equals(args)); verify(suitesList.size() == 7); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); verify(suitesList.get(2).equals(args)); verify(suitesList.get(3).equals(args)); verify(suitesList.get(4).equals(args)); verify(suitesList.get(5).equals(args)); verify(suitesList.get(6).equals(args)); }
Conclusione
Approccio 1, testare i metodi privati indirettamente testando i metodi a livello di pacchetto, protetti e pubblici che li chiamano, sarà spesso l’approccio migliore. Nei casi in cui si desidera testare direttamente i metodi privati, utilizzare reflection per testare i metodi privati, sebbene piuttosto ingombranti, fornisce la separazione più pulita del codice di test dal codice di produzione e il minimo impatto sul codice di produzione. Tuttavia, se non ti dispiace fare quei particolari metodi privati che vuoi testare l’accesso ai pacchetti, puoi usare l’approccio 2. Oppure, se non ti dispiace posizionare una classe di test nidificata all’interno della classe di produzione in fase di test, l’approccio 3 ti consente almeno di mantenere privati i metodi privati.
Non c’è una risposta perfetta. Ma se adotti l’approccio 4, alla fine finirai con una manciata di metodi come invokeStaticMethod
che puoi riutilizzare. Una volta scritto un metodo di convenienza, come invokeParseArgsIntoLists
, per un metodo privato, è possibile scrivere test contro il metodo privato senza troppe difficoltà.
Risorse
1. L’API ServiceUI definisce un modo standard per collegare interfacce utente ai servizi Jini:
http://www.artima.com/jini/serviceui/index.html
2. Daniel Steinberg è attualmente il redattore capo di Java.NET:
http://www.java.net/
3. Artima SuiteRunner è un toolkit di test open source gratuito e JUnit runner:
http://www.artima.com/suiterunner/index.html
4.JUnit FAQ domanda sul test dei metodi privati:
http://junit.sourceforge.net/doc/faq/faq.htm#tests_10
5. Testare metodi privati( non farlo), un post sul blog di Charles Miller:
http://fishbowl.pastiche.org/2003/03/28/testing_private_methods_dont_do_it
6. Andy Hunt e Dave Thomas sono gli autori di Pragmatic Unit Testing, che è disponibile presso il Pragmatic Store.
7. JUnit Addons è una raccolta di classi di supporto per JUnit creato da Vladimar R. Bossicard:
http://sourceforge.net/projects/junit-addons
PrivateAccessor
è la classe di JUnit Addons che facilita il test dei membri privati:
http://junit-addons.sourceforge.net/junitx/util/PrivateAccessor.html
9.Classe PrivilegedAccessor, che è possibile utilizzare per accedere ai membri privati:
http://groups.yahoo.com/group/junit/files/src/PrivilegedAccessor.java
10. Test Driven Development by Example, di Kent Beck, descrive la tecnica di test-first:
http://www.amazon.com/exec/obidos/ASIN/0321146530/
11. Prova Infetto, da Kent Beck e Erich Gamma, introdotto JUnit per il mondo:
http://junit.sourceforge.net/doc/testinfected/testing.htm
di Unit Testing Privato Metodi, un blog post su nUnit da Ted Graham:
http://weblogs.asp.net/tgraham/archive/2003/12/31/46984.aspx
Sovvertire Java Protezione di Accesso per i Test di Unità, un O’Reilly OnJava.com articolo di Ross Burton:
http://www.onjava.com/pub/a/onjava/2003/11/12/reflection.html
Classe RunnerSuite
, da cui i frammenti di codice in questo articolo sono state scattate, appare nella sua interezza qui:
http://www.artima.com/suiterunner/privateExample.html
Perché Abbiamo Rielaborato JUnit
http://www.artima.com/suiterunner/why.html
Artimio SuiteRunner Tutorial, la Costruzione di Conformità e Test di Unità con Artimio SuiteRunner:
http://www.artima.com/suiterunner/tutorial.html
iniziare con Artimio SuiteRunner, Come Eseguire il Semplice Esempio Incluso nella Distribuzione:
http://www.artima.com/suiterunner/start.html
Runnning Test JUnit con Artimio SuiteRunner, come utilizzare Artimio SuiteRunner come JUnit runner a correre esistenti suite di test JUnit:
http://www.artima.com/suiterunner/junit.html
Artimio SuiteRunner home page:
http://www.artima.com/suiterunner/index.html
Artimio SuiteRunner pagina di download (È necessario accedere a Artima.com per scaricare il comunicato):
http://www.artima.com/suiterunner/download.jsp
Il SuiteRunner Forum:
http://www.artima.com/forums/forum.jsp?forum=61
Talk back!
Hai un parere? I lettori hanno già pubblicato 26 commenti su questo articolo. Perché non aggiungere il tuo?
Informazioni sull’autore
Bill Venners è presidente di Artima Software, Inc. e caporedattore di Artima.com. È autore del libro Inside the Java Virtual Machine, un’indagine programmer-oriented sull’architettura e gli interni della piattaforma Java. Le sue colonne popolari nella rivista JavaWorld coprivano interni Java, design orientato agli oggetti e Jini. Bill è stato attivo nella comunità Jini fin dal suo inizio. Ha guidato il progetto ServiceUI della comunità Jini che ha prodotto l’API ServiceUI. Il ServiceUI è diventato il modo standard de facto per associare le interfacce utente ai servizi Jini, ed è stato il primo standard comunitario Jini approvato tramite il processo decisionale Jini. Bill serve anche come membro eletto del Comitato di supervisione tecnica iniziale della comunità Jini (TOC), e in questo ruolo ha contribuito a definire il processo di governance per la comunità. Attualmente dedica la maggior parte delle sue energie alla costruzione Artima.com in una risorsa sempre più utile per gli sviluppatori.