Badanie jednostkowe z użyciem singletonów

Przygotowałem kilka automatycznych testów z Visual Studio Team Edition testing framework. Chcę, aby jeden z testów połączył się z bazą danych w normalny sposób w Programie:

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

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


Dzięki za odpowiedzi. Wszystkie z nich były bardzo pouczające.
Author: Pure.Krome, 2010-01-18

4 answers

 88
Author: Gregory Pakosz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 12:36:23

Możesz użyć konstruktora dependency injection. Przykład:

public class SingletonDependedClass
{
    private string _ProviderName;

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

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

Który pozwala przekazać łańcuch połączeń bezpośrednio do obiektu podczas testowania.

Również jeśli używasz Visual Studio Team Edition testing framework możesz uczynić konstruktor z parametrem prywatnym i przetestować klasę za pośrednictwem accesora.

Właściwie rozwiązuję tego typu problemy z wyśmiewaniem. Przykład:

Masz klasę, która zależy od Singletona:

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

Po pierwsze SingletonDependedClass musi być refactored to take Singleton instance as constructor parameter:

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 (biblioteka MOQ jest używana):

[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);
    ...
}
 14
Author: bniwredyc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 13:16:58

Przykład z książki: efektywna praca z kodem starszym

Tutaj też udzielono tej samej odpowiedzi: https://stackoverflow.com/a/28613595/929902

Aby uruchomić kod zawierający singletony w wiązce testowej, musimy rozluźnić właściwość singleton. Zrobimy tak. Pierwszym krokiem jest dodanie nowej statycznej metody do klasy singleton. Metoda pozwala nam zastąpić statyczną instancję 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ć testową instancję PermitRepository i ustawić go. Chcielibyśmy napisać taki kod w naszej konfiguracji testowej:

public void setUp() {
    PermitRepository repository = PermitRepository.getInstance();
    ...
    // add permits to the repository here
    ...
    PermitRepository.setTestingInstance(repository);
}
 10
Author: Teoman shipahi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:43

Masz tu do czynienia z bardziej ogólnym problemem. W przypadku niewłaściwego użycia Singletony utrudniają testabiliy.

Wykonałem szczegółową analizę tego problemu w kontekście niezwiązanego projektu. Postaram się podsumować moje punkty:

  1. Jeśli Twój Singleton ma znaczący stan globalny, nie używaj Singletona. Obejmuje to trwałe przechowywanie, takie jak bazy danych, pliki itp.
  2. w przypadkach, gdy zależność od obiektu Singleton nie jest oczywista przez nazwę klasy, zależność powinna być wstrzykiwana. Potrzeba wprowadzania instancji Singletona do klas dowodzi niewłaściwego użycia wzorca (patrz punkt 1).
  3. zakłada się, że cykl życia Singletona jest taki sam jak w przypadku aplikacji. Większość implementacji Singletona używa mechanizmu lazy-load do tworzenia instancji. Jest to trywialne i ich cykl życia jest mało prawdopodobne, aby zmienić, lub nie należy używać Singleton.
 5
Author: Johannes Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 12:56:27