2010-01-18 24 views
39

Przygotowałem kilka automatycznych testów w ramach testowania Visual Studio Team Edition. Chcę jeden z testów, aby połączyć się z bazą danych po normalny sposób odbywa się to w ramach programu:Testy jednostkowe z singletonami

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

Ale otrzymuję wyjątek w tej linii. Przypuszczam, że tak się dzieje, ponieważ ConfigurationManager jest singleton. Jak można obejść problem singletonu z testami jednostkowymi?


Dzięki za odpowiedzi. Wszyscy oni byli bardzo pouczający.

+1

Jaki jest dokładny komunikat o błędzie? – Kane

+0

Wyjątek wskaźnika zerowego – yeyeyerman

Odpowiedz

69
+1

Wszystkie te odniesienia odnoszą się do problemu głębiej, niż mogłem podsumować w odpowiedzi. Są zdecydowanie świetne. –

+2

+1! Zobacz także książkę "Working Effectively with Legacy Code" autorstwa Michaela Feathersa; dostarcza techniki do testowania za pomocą Singletonów. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 – TrueWill

+0

Szczególnie interesujący jest link * Performant Singletons *, prowadzący do strony błędu ** 403 Forbidden **. Dość subtelny sposób wyrażania odrzucenia. – derM

13

Można użyć wstrzyknięcia zależności od konstruktora. Przykład:

public class SingletonDependedClass 
{ 
    private string _ProviderName; 

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

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

Umożliwia to przekazanie łańcucha połączenia bezpośrednio do obiektu podczas testowania.

Również, jeśli używasz środowiska testowego Visual Studio Team Edition, możesz utworzyć konstruktor z parametrem private i przetestować klasę przez akcesor.

W rzeczywistości rozwiązuję tego rodzaju problemy z kpiną. Przykład:

masz klasy, który zależy od 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; 
     ... 
    } 
} 

przede wszystkim SingletonDependedClass potrzeby być refactored do podjęcia Singleton instancji jako parametr konstruktora:

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 SingletonDependedClass (Moq mocking library jest używany):

[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

Masz teraz do czynienia z bardziej ogólnym problemem. Jeśli użyte w niewłaściwy sposób, Singletons utrudniają testowanie.

Zrobiłem detailed analysis tego problemu w kontekście rozłączenia projektu.Spróbuję podsumować moje punkty:

  1. Jeśli twój Singleton ma istotny globalny stan, nie używaj Singleton. Obejmuje to trwałe przechowywanie, takie jak bazy danych, pliki itp.
  2. W przypadkach, gdy zależność od obiektu Singleton nie jest oczywista po nazwie klasy, należy wprowadzić zależność. Potrzeba wstrzykiwania Singleton Instances na zajęcia świadczy o niewłaściwym użyciu wzoru (patrz punkt 1).
  3. Przyjmuje się, że cykl życia Singleton jest taki sam, jak w aplikacji. Większość implementacji Singleton używa mechanizmu Lazy-Load do tworzenia instancji. To jest trywialne, a ich cykl życiowy raczej się nie zmieni, inaczej nie powinieneś używać Singleton.
7

Przykład z Księgi: Working Effectively with Legacy Code

podano także sama odpowiedź tutaj: https://stackoverflow.com/a/28613595/929902

uruchomić kod zawierający singletons w uprzęży testu, musimy odpocząć właściwość singleton. Oto, jak to robimy. Pierwszym krokiem jest dodanie nowej metody statycznej do klasy singleton. Metoda pozwala nam zastąpić instancję statyczną w singletonie. Nazwiemy to 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) { 
    ... 
    } 
    ... 
} 

Teraz, gdy mamy ten setter, możemy utworzyć instancję testowanie PermitRepository i ustawić ją. Chcielibyśmy napisać taki kod w naszym ustawieniu testowym:

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

Wygląda na to, że czegoś brakuje tutaj w metodzie setUp(). PermitRepository ma prywatny konstruktor, więc nie możesz używać tam nowych ... – leogtzr

+0

W swojej metodzie instalacji wywołujesz get instance przed ustawieniem instancji testowej. Najpierw chcesz ustawić swoją instancję testową. –