Come deridere uno statico Singleton?

Ho un numero di classi che mi è stato chiesto di aggiungere alcuni test unitari a Rhino Mocks e ad avere alcuni problemi.

Prima di tutto, so che RhinoMocks non ammette la derisione dei membri di Static. Sto cercando le opzioni che ho (oltre a usare TypeMock).

Un esempio della class che ho è simile al seguente:

class Example1 : ISomeInterface { private static ISomeInterface _instance; private Example1() { // set properties via private static methods } static Example1() { _instance = new Example1(); } public static ISomeInterface Instance() { get { return _instance; } } // Instance properties // Other Instance Properties that represent objects that follow a similar pattern. } 

Quindi quando chiamo la class di cui sopra, sembra qualcosa del genere …

 Example1.Instance.SomeObject.GoDownARabbitHole(); 

C’è un modo per farmi deridere SomeObject.GoDownARabbitHole() in questa situazione o SomeObject.GoDownARabbitHole() in giro l’istanza?

I singleton sono in disaccordo con Testability perché sono così difficili da cambiare. Sarebbe molto meglio usare Dependency Injection per iniettare un’istanza di ISomeInterface nelle tue classi di consumo:

 public class MyClass { private readonly ISomeInterface dependency; public MyClass(ISomeInterface dependency) { if(dependency == null) { throw new ArgumentNullException("dependency"); } this.dependency = dependency; } // use this.dependency in other members } 

Nota come Guard Claus e la parola chiave readonly garantiscono che l’istanza di ISomeInterface sarà sempre disponibile.

Ciò consentirà di utilizzare Rhino Mocks o un’altra libreria di simulazione dynamic per iniettare Test duplicati di ISomeInterface nelle classi di consumo.

Scoraggiato da discussioni come questa, mi ci è voluto un po ‘di tempo per notare che i singleton non sono così difficili da deridere. Dopo tutto, perché stiamo usando c #?

Basta usare Reflection.

Con il codice di esempio fornito è necessario assicurarsi che venga chiamato il costruttore statico prima di impostare il campo statico sull’object mocked. Altrimenti potrebbe sovrascrivere il tuo object deriso. Basta chiamare qualsiasi cosa sul singleton che non ha alcun effetto prima di impostare il test.

 ISomeInterface unused = Singleton.Instance(); System.Reflection.FieldInfo instance = typeof(Example1).GetField("_instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); Mock mockSingleton = new Mock(); instance.SetValue(null, mockSingleton.Object); 

Ho fornito il codice per il mocking con Moq, ma immagino che Rhino Mocks sia abbastanza simile.

Ecco un approccio low-touch che utilizza un delegato, che può essere impostato inizialmente e modificato in fase di runtime. È meglio spiegato con l’esempio (in particolare, mocking DateTime.Now):

http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/11/09/systemtime-versus-isystemclock-dependencies-revisited.aspx

Esempio dal libro : funziona in modo efficace con il codice legacy

Per eseguire codice contenente singleton in un’imbracatura di test, dobbiamo rilassare la proprietà singleton. Ecco come lo facciamo. Il primo passaggio consiste nell’aggiungere un nuovo metodo statico alla class singleton. Il metodo ci consente di sostituire l’istanza statica nel singleton. Lo chiameremo setTestingInstance .

 public class PermitRepository { private static PermitRepository instance = null; private PermitRepository() {} public static void setTestingInstance(PermitRepository newInstance) { instance = newInstance; } public static PermitRepository getInstance() { if (instance == null) { instance = new PermitRepository(); } return instance; } public Permit findAssociatedPermit(PermitNotice notice) { ... } ... } 

Ora che abbiamo quel setter, possiamo creare un’istanza di test di un PermitRepository e impostarlo. Vorremmo scrivere un codice come questo nella nostra configurazione di prova:

 public void setUp() { PermitRepository repository = new PermitRepository(); ... // add permits to the repository here ... PermitRepository.setTestingInstance(repository); } 

Puoi prendere in giro l’interfaccia, ISomeInterface. Quindi, refactoring il codice che lo usa per usare dependency injection per ottenere il riferimento all’object singleton. Ho incontrato questo problema molte volte nel nostro codice e questa soluzione mi piace di più.

per esempio:

 public class UseTheSingleton { private ISomeInterface myX; public UseTheSingleton(ISomeInterface x) { myX = x; } public void SomeMethod() { myX. } } 

Poi …

 UseTheSingleton useIt = UseTheSingleton(Example1.Instance); 

Controlla l’ iniezione di dipendenza .

L’hai già iniziato, ma per le classi difficili da testare (statistiche ecc.) Puoi usare il modello di progettazione adapter per scrivere un wrapper attorno a questo codice difficile da testare. Utilizzando l’ interface di questo adattatore, è quindi ansible testare il codice in isolamento.

Per qualsiasi consiglio sui test delle unità e altri problemi di test, consulta il Google Testing Blog , in particolare gli articoli di Misko.

Esempio

Dici che stai scrivendo dei test, quindi potrebbe essere troppo tardi, ma potresti rifattorizzare la statica sull’istanza? O c’è una vera ragione per cui detta class dovrebbe rimanere statica?

Non è necessario correggere tutti gli usi contemporaneamente, solo quello con cui si ha a che fare ora. Aggiungi un campo ISomeInterface alla class in prova e impostalo tramite il costruttore. Se stai usando Resharper (stai usando Resharper, vero?), La maggior parte di questo sarà banale da fare. Se questo è davvero poco pratico, è ansible avere più di un costruttore, uno che imposta il nuovo campo di dipendenza, l’altro che chiama il primo con il singleton come valore predefinito.