prendendo in giro una class singleton

Recentemente ho letto che fare un singleton di class rende imansible prendere in giro gli oggetti della class, il che rende difficile testare i suoi clienti. Non ho potuto capire immediatamente la ragione sottostante. Qualcuno può spiegare cosa rende imansible prendere in giro una class singleton? Inoltre, ci sono altri problemi associati alla creazione di un singleton di class?

Certo, potrei scrivere qualcosa come non usare il singleton, sono malvagi, usare Guice / Spring / qualunque cosa ma prima, questo non risponderebbe alla tua domanda e in secondo luogo, a volte devi trattare con singleton, quando usi il codice legacy per esempio.

Quindi, non parliamo del buono o del cattivo di Singleton (c’è un’altra domanda per questo) ma vediamo come gestirli durante il test. Per prima cosa, esaminiamo un’implementazione comune del singleton:

public class Singleton { private Singleton() { } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } public String getFoo() { return "bar"; } } 

Ci sono due problemi di test qui:

  1. Il costruttore è privato quindi non possiamo estenderlo (e non possiamo controllare la creazione di istanze nei test ma, beh, questo è il punto dei singleton).

  2. getInstance è statico, quindi è difficile iniettare un falso al posto dell’object singleton nel codice usando il singleton .

Per i modelli di derisione basati su ereditarietà e polimorfismo, entrambi i punti sono ovviamente grandi problemi. Se hai il controllo del codice, un’opzione è quella di rendere il tuo singleton “più testabile” aggiungendo un setter che permetta di modificare il campo interno come descritto in Learn to Stop Worrying e Love the Singleton (non ti serve nemmeno un beffardo quadro in quel caso). In caso contrario, i moderni sistemi di derisione basati sui concetti di intercettazione e AOP consentono di superare i problemi menzionati in precedenza.

Ad esempio, Mocking Static Method Calls mostra come deridere un Singleton usando JMockit Expectations .

Un’altra opzione sarebbe quella di usare PowerMock , un’estensione per Mockito o JMock che permette di prendere in giro cose che normalmente non sono raggelabili come i metodi statici, finali, privati ​​o di costruzione. Inoltre è ansible accedere agli interni di una class.

Il modo migliore per deridere un singleton non è usarli affatto, o almeno non nel senso tradizionale. Alcune pratiche che potresti voler cercare sono:

  • programmazione alle interfacce
  • iniezione di dipendenza
  • inversione di controllo

Quindi piuttosto che avere un singolo accederai in questo modo:

 Singleton.getInstance().doSometing(); 

… definisce il tuo “singleton” come un’interfaccia e ha qualcos’altro che gestisce il suo ciclo di vita e lo inserisce dove ti serve, ad esempio come variabile di istanza privata:

 @Inject private Singleton mySingleton; 

Quindi, quando esegui il collaudo della class / componenti / etc che dipendono dal singleton, puoi facilmente iniettarne una versione finta.

La maggior parte dei contenitori di iniezione delle dipendenze consente di contrassegnare un componente come “singleton”, ma è compito del contenitore gestirlo.

L’utilizzo delle procedure sopra descritte rende molto più semplice testare il codice dell’unità e consente di concentrarsi sulla logica funzionale anziché sulla logica di cablaggio. Significa anche che il tuo codice inizia davvero a diventare veramente orientato agli oggetti, in quanto qualsiasi uso di metodi statici (compresi i costruttori) è procedurale debitamente. In questo modo i componenti iniziano a diventare veramente riutilizzabili.

Scopri Google Guice come antipasto per 10:

http://code.google.com/p/google-guice/

Potresti anche guardare Spring e / o OSGi che possono fare questo genere di cose. C’è un sacco di cose IOC / DI là fuori. 🙂

A Singleton, per definizione, ha esattamente un’istanza. Quindi la sua creazione è strettamente controllata dalla class stessa. In genere è una class concreta, non un’interfaccia e, a causa del suo costruttore privato, non è sottoclassabile. Inoltre, viene trovato triggersmente dai suoi client (chiamando Singleton.getInstance() o un equivalente), quindi non è ansible utilizzare facilmente ad esempio Dependency Injection per sostituire la sua istanza “reale” con un’istanza di simulazione:

 class Singleton { private static final myInstance = new Singleton(); public static Singleton getInstance () { return myInstance; } private Singleton() { ... } // public methods } class Client { public doSomething() { Singleton singleton = Singleton.getInstance(); // use the singleton } } 

Per i mocker, avresti idealmente bisogno di un’interfaccia che possa essere liberamente sottoclass e la cui implementazione concreta sia fornita ai suoi clienti mediante l’iniezione di dipendenza.

Puoi rilassare l’implementazione di Singleton per renderla testabile da

  • fornendo un’interfaccia che può essere implementata da una sottoclass simulata e da una “vera”
  • aggiunta di un metodo setInstance per consentire la sostituzione dell’istanza nei test unitari

Esempio:

 interface Singleton { private static final myInstance; public static Singleton getInstance() { return myInstance; } public static void setInstance(Singleton newInstance) { myInstance = newInstance; } // public method declarations } // Used in production class RealSingleton implements Singleton { // public methods } // Used in unit tests class FakeSingleton implements Singleton { // public methods } class ClientTest { private Singleton testSingleton = new FakeSingleton(); @Test public void test() { Singleton.setSingleton(testSingleton); client.doSomething(); // ... } } 

Come puoi vedere, puoi rendere testabile la tua unità codice usando Singleton compromettendo la “pulizia” di Singleton. Alla fine, è meglio non usarlo affatto se puoi evitarlo.

Aggiornamento: Ed ecco il riferimento obbligatorio a Working Effectively With Legacy Code di Michael Feathers.

Dipende molto dall’implementazione singleton. Ma soprattutto perché ha un costruttore privato e quindi non è ansible estenderlo. Ma tu hai la seguente opzione

  • crea un’interfaccia – SingletonInterface
  • fai in modo che la tua class singleton implementa quell’interfaccia
  • lasciare Singleton.getInstance() restituire SingletonInterface
  • fornire un’implementazione SingletonInterface di SingletonInterface nei test
  • impostalo nel campo private static su Singleton usando la reflection.

Ma è meglio evitare singleton (che rappresentano uno stato globale). Questa conferenza spiega alcuni importanti concetti di design dal punto di vista della testabilità.

Non è che il modello di Singleton sia esso stesso puro male, ma che è massicciamente abusato anche in situazioni in cui è inapproriato. Molti sviluppatori pensano “Oh, probabilmente avrò solo bisogno di uno di questi, quindi facciamolo un singleton”. In effetti dovresti pensare “Probabilmente avrò solo bisogno di uno di questi, quindi costruiamo uno all’inizio del mio programma e passa i riferimenti dove è necessario”.

Il primo problema con Singleton e testing non è tanto per il singleton, ma per la pigrizia. A causa della comodità di ottenere un singleton, la dipendenza dall’object singleton è spesso incorporata direttamente nei metodi che rende molto difficile cambiare il singleton in un altro object con la stessa interfaccia ma con un’implementazione differente (ad esempio, un object fittizio ).

Invece di:

 void foo() { Bar bar = Bar.getInstance(); // etc... } 

preferire:

 void foo(IBar bar) { // etc... } 

Ora puoi testare la funzione foo con un object barato che puoi controllare. Hai rimosso la dipendenza in modo da poter testare foo senza bar test.

L’altro problema con singleton e testing è quando si verifica il singleton stesso. Un singleton è (in base alla progettazione) molto difficile da ribuild, quindi per esempio è ansible testare una sola volta il controlo singleton. È anche ansible che la singola istanza di Bar mantenga lo stato tra i test, causando il successo o l’insuccesso a seconda dell’ordine in cui vengono eseguiti i test.

C’è un modo per deridere Singleton. Usa powermock per simulare il metodo statico e utilizzare Whitebox per richiamare il costruttore YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper); YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);

Quello che sta accadendo è che il codice byte Singleton sta cambiando in fase di esecuzione.

godere