Модульное тестирование с синглетонами - PullRequest
45 голосов
/ 18 января 2010

Я подготовил несколько автоматических тестов с помощью среды тестирования Visual Studio Team Edition. Я хочу, чтобы один из тестов подключился к базе данных в обычном порядке, как это делается в программе:

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

Но я получаю исключение в этой строке. Я полагаю, это происходит потому, что ConfigurationManager является одноэлементным. Как вы можете обойти проблему синглтона в модульных тестах?


Спасибо за ответы. Все они были очень поучительны.

Ответы [ 4 ]

13 голосов
/ 18 января 2010

Вы можете использовать конструктор внедрения зависимостей. Пример:

public class SingletonDependedClass
{
    private string _ProviderName;

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

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

Это позволяет передавать строку подключения непосредственно к объекту во время тестирования.

Также, если вы используете среду тестирования Visual Studio Team Edition, вы можете сделать конструктор с параметром private и протестировать класс с помощью метода доступа.

На самом деле я решаю подобные проблемы с насмешками. Пример:

У вас есть класс, который зависит от синглтона:

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

Прежде всего SingletonDependedClass необходимо реорганизовать, чтобы принять Singleton экземпляр в качестве параметра конструктора:

public class SingletonDependedClass
{    
    private Singleton _SingletonInstance;

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

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

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

Тест SingletonDependedClass (используется библиотека Moq ):

[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);
    ...
}
9 голосов
/ 19 февраля 2015

Пример из Книги : Эффективная работа с устаревшим кодом

Также дан тот же ответ здесь: https://stackoverflow.com/a/28613595/929902

Чтобы запустить код, содержащий синглтоны, в тестовом жгуте, нам нужно ослабить свойство singleton. Вот как мы это делаем. Первым шагом является добавление нового статического метода в одноэлементный класс. Метод позволяет нам заменить статический экземпляр в синглтоне. Мы назовем это 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) {
    ...
    }
    ...
}

Теперь, когда у нас есть этот установщик, мы можем создать тестовый экземпляр PermitRepository и установите его. Мы хотели бы написать такой код в нашей тестовой настройке:

public void setUp() {
    PermitRepository repository = PermitRepository.getInstance();
    ...
    // add permits to the repository here
    ...
    PermitRepository.setTestingInstance(repository);
}
5 голосов
/ 18 января 2010

Здесь вы столкнулись с более общей проблемой.При неправильном использовании синглтоны мешают тестабилию.

Я провел подробный анализ этой проблемы в контексте развязанного дизайна.Я постараюсь обобщить мои баллы:

  1. Если ваш синглтон имеет значительное глобальное состояние, не используйте синглтон.Это включает постоянное хранилище, такое как базы данных, файлы и т. Д.
  2. В случаях, когда зависимость от объекта Singleton не очевидна по имени класса, зависимость должна быть введена.Необходимость внедрения Singleton Instances в классы доказывает неправильное использование шаблона (см. Пункт 1).
  3. Жизненный цикл синглтона считается таким же, как и у приложения.Большинство реализаций Singleton используют механизм отложенной загрузки для создания своих экземпляров.Это тривиально, и их жизненный цикл вряд ли изменится, иначе вы не должны использовать Singleton.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...