2010-01-18 16 views
39

Ich habe einige automatische Tests mit dem Visual Studio Team Edition-Testframework vorbereitet. Ich mag einer der Tests in die Datenbank nach der normalen Art und Weise verbinden Sie es im Programm durchgeführt wird:Komponententests mit Singletons

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

Aber erhalte ich eine Ausnahme in dieser Linie. Ich nehme an, das passiert, weil der ConfigurationManager ein Singleton ist. Wie können Sie das Singleton Problem mit Komponententests umgehen?


Danke für die Antworten. Sie alle waren sehr lehrreich.

+1

Was ist die genaue Fehlermeldung? – Kane

+0

Nullzeiger Ausnahme – yeyeyerman

Antwort

69
+1

Alle diese Referenzen adressieren das Problem tiefer, dass ich in einer Antwort zusammenfassen könnte. Sie sind auf jeden Fall großartig. –

+2

+1! Siehe auch das Buch "Effectively with Legacy Code" von Michael Feathers; Er bietet Techniken zum Testen mit Singletons. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 – TrueWill

+0

Besonders interessant ist der Link * Performant Singletons *, der zu einer ** 403 Forbidden ** Fehlerseite führt. Ziemlich subtile Art, Ablehnung auszudrücken. – derM

13

Sie können die Konstruktorabhängigkeitsinjektion verwenden. Beispiel:

public class SingletonDependedClass 
{ 
    private string _ProviderName; 

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

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

Damit können Sie die Verbindungszeichenfolge während des Tests direkt an das Objekt übergeben.

Auch wenn Sie das Visual Studio Team Edition-Testframework verwenden, können Sie den Konstruktor mit dem Parameter private erstellen und die Klasse über den Accessor testen.

Eigentlich löse ich diese Art von Problemen mit Mocking. Beispiel:

Sie haben eine Klasse, die auf Singletons abhängt:

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; 
     ... 
    } 
} 

Erste aller Singleton Instanz als Konstruktor Parameter nehmen Refactoring werden SingletonDependedClass Bedürfnisse:

public class SingletonDependedClass 
{  
    private Singleton _SingletonInstance; 

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

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

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

Test von SingletonDependedClass (Moq mocking library verwendet wird):

[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

Sie stehen hier vor einem allgemeineren Problem. Wenn sie missbraucht werden, behindern Singletons die Testabilität.

Ich habe eine detailed analysis dieses Problem im Zusammenhang mit einem entkoppelten Design getan.Ich werde versuchen, meine Punkte zusammenzufassen:

  1. Wenn Ihr Singleton einen signifikanten globalen Zustand trägt, verwenden Sie nicht Singleton. Dazu gehören persistente Speicher wie Datenbanken, Dateien usw.
  2. In Fällen, in denen die Abhängigkeit von einem Singleton-Objekt durch den Klassennamen nicht offensichtlich ist, sollte die Abhängigkeit eingefügt werden. Die Notwendigkeit, Singleton-Instanzen in Klassen zu injizieren, beweist eine falsche Verwendung des Musters (siehe Punkt 1).
  3. Es wird angenommen, dass der Lebenszyklus eines Singleton mit dem der Anwendung übereinstimmt. Die meisten Singleton-Implementierungen verwenden einen Lazy-Load-Mechanismus, um sich selbst zu instanziieren. Das ist trivial und ihr Lebenszyklus wird sich wahrscheinlich nicht ändern, oder Sie sollten Singleton nicht verwenden.
7

Beispiel von Buch: Working Effectively with Legacy Code

auch gleiche Antwort hier gegeben: https://stackoverflow.com/a/28613595/929902

Um Code enthält Singletons in einer Testumgebung ausgeführt werden, müssen wir die Singleton-Eigenschaft entspannen. So machen wir es. Der erste Schritt besteht darin, der Singleton-Klasse eine neue statische Methode hinzuzufügen. Die Methode ermöglicht es uns, die statische Instanz im Singleton zu ersetzen. Wir nennen es 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) { 
    ... 
    } 
    ... 
} 

Nun, da wir diesen Setter haben, können wir eine Test Instanz eines PermitRepository erstellen und festlegen. Wir würden gerne Code wie diesen in unserem Test-Setup schreiben:

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

Es scheint hier etwas in der Methode setUp() zu fehlen. PermitRepository verfügt über einen privaten Konstruktor, sodass Sie dort keine neuen Konstrukte verwenden können. – leogtzr

+0

In Ihrer Setup-Methode rufen Sie die Instanz ab, bevor Sie Ihre Testinstanz festlegen. Sie möchten zuerst Ihre Testinstanz festlegen. –