2010-01-18 28 views
39

Ho preparato alcuni test automatici con il framework di test Visual Studio Team Edition. Voglio uno dei test per la connessione al database seguendo la via normale è fatto nel programma:Test unitari con singleton

string r_providerName = ConfigurationManager.ConnectionStrings["main_db"].ProviderName; 

Ma io ricevo un'eccezione in questa linea. Suppongo che questo accada perché il ConfigurationManager è un singleton. Come puoi risolvere il problema del singleton con i test unitari?


Grazie per le risposte. Tutti sono stati molto istruttivi.

+1

Qual è l'esatto messaggio di errore? – Kane

+0

Eccezione puntatore nullo – yeyeyerman

risposta

69
+1

Tutti questi riferimenti risolvono il problema in modo più approfondito che potrei riassumere in una risposta. Sono decisamente fantastici. –

+2

+1! Vedi anche il libro "Lavorare efficacemente con il codice legacy" di Michael Feathers; fornisce tecniche per testare con Singletons. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 – TrueWill

+0

Particolarmente interessante è il link * Performant Singletons *, che porta a una pagina di errore ** 403 Proibita **. Un modo sottile per esprimere il rifiuto. – derM

13

È possibile utilizzare l'iniezione delle dipendenze del costruttore. Esempio:

public class SingletonDependedClass 
{ 
    private string _ProviderName; 

    public SingletonDependedClass() 
     : this(ConfigurationManager.ConnectionStrings["main_db"].ProviderName) 
    { 
    } 

    public SingletonDependedClass(string providerName) 
    { 
     _ProviderName = providerName; 
    } 
} 

che permette di passare stringa di connessione direttamente a oggetto durante il test.

Inoltre, se si utilizza il framework di test di Visual Studio Team Edition, è possibile rendere il costruttore con parametro private e testare la classe tramite l'accessor.

In realtà risolvo questo tipo di problemi con il beffardo. Esempio:

di avere una classe che dipende Singleton:

public class Singleton 
{ 
    public virtual string SomeProperty { get; set; } 

    private static Singleton _Instance; 
    public static Singleton Insatnce 
    { 
     get 
     { 
      if (_Instance == null) 
      { 
       _Instance = new Singleton(); 
      } 

      return _Instance; 
     } 
    } 

    protected Singleton() 
    { 
    } 
} 

public class SingletonDependedClass 
{ 
    public void SomeMethod() 
    { 
     ... 
     string str = Singleton.Insatnce.SomeProperty; 
     ... 
    } 
} 

Prima di tutto SingletonDependedClass ha bisogno di essere riscritta a prendere Singleton istanza come parametro del costruttore:

public class SingletonDependedClass 
{  
    private Singleton _SingletonInstance; 

    public SingletonDependedClass() 
     : this(Singleton.Insatnce) 
    { 
    } 

    private SingletonDependedClass(Singleton singletonInstance) 
    { 
     _SingletonInstance = singletonInstance; 
    } 

    public void SomeMethod() 
    { 
     string str = _SingletonInstance.SomeProperty; 
    } 
} 

prova di SingletonDependedClass (Moq mocking library viene utilizzato):

[TestMethod()] 
public void SomeMethodTest() 
{ 
    var singletonMock = new Mock<Singleton>(); 
    singletonMock.Setup(s => s.SomeProperty).Returns("some test data"); 
    var target = new SingletonDependedClass_Accessor(singletonMock.Object); 
    ... 
} 
5

Sei di fronte a un problema più generale qui. Se usati male, Singletons ostacola la verifica.

Ho eseguito uno detailed analysis di questo problema nel contesto di un progetto disaccoppiato.Proverò a riassumere i miei punti:

  1. Se il Singleton ha uno stato globale significativo, non utilizzare Singleton. Ciò include archiviazione persistente come database, file, ecc.
  2. Nei casi in cui la dipendenza da un oggetto Singleton non è evidente dal nome della classe, la dipendenza deve essere iniettata. La necessità di iniettare le istanze di Singleton in classi dimostra un uso errato del modello (vedi punto 1).
  3. Si presume che il ciclo di vita di Singleton sia uguale a quello dell'applicazione. La maggior parte delle implementazioni Singleton utilizza un meccanismo di caricamento lento per creare istanze. Questo è banale e il loro ciclo di vita è improbabile che cambi, altrimenti non dovresti usare Singleton.
7

Esempio da libro: Working Effectively with Legacy Code

anche dato stessa risposta qui: https://stackoverflow.com/a/28613595/929902

per eseguire codice contenente singletons in un test harness, dobbiamo rilassare la proprietà Singleton. Ecco come lo facciamo. Il primo passaggio consiste nell'aggiungere un nuovo metodo statico alla classe 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 che setter, possiamo creare un'istanza di sperimentazione di un PermitRepository e impostarlo. Vorremmo scrivere un codice come questo nella nostra configurazione di prova:

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

Sembra che manchi qualcosa nel metodo setUp(). PermitRepository ha un costruttore privato, quindi non puoi usare nuovo lì ... – leogtzr

+0

Nel tuo metodo di installazione, stai chiamando get instance prima di impostare l'istanza di test. Si desidera impostare prima l'istanza di test. –