Синглтоны проблематичны с точки зрения модульного тестирования.Я предполагаю, что классы, использующие ваш Singleton, извлекают экземпляр через статическое свойство:
class MyClass()
{
public MyClass()
{
var myService = StaticFoo.MyServiceInstance;
}
}
Это эквивалентно созданию конкретного экземпляра класса локально, поскольку знание того, «как добраться» до вашего Singleton, является частьюВаша реализация класса.Что вам нужно будет сделать, это удалить эти знания и внедрить эту зависимость так же, как вы вводите другие зависимости от не-синглетонов.Самым простым способом было бы внедрение конструктора:
class MyClass()
{
public MyClass(IService myService)
{
//..
}
}
Другая необходимая часть - это либо убедиться, что все методы / свойства интерфейса в вашем сервисе определены как виртуальные, либо определить интерфейс или абстрактный базовый класс, который ваш сервисреализует и определяет все операции, которые ваш класс MyClass
использует с этим сервисом.Это необходимо, поскольку большинство фреймворков для модульного тестирования (включая Rhino-Mocks) могут только макетировать виртуальные методы / свойства.
Альтернатива, которую я видел, но лично мне не очень нравится, - это установка сеттера в вашем синглтоне, чтобы вы моглиможет "поменять" конкретный класс с вашим фиктивным объектом, необходимым для модульного тестирования.Этот установщик будет использоваться только для модульного тестирования и может быть помечен как внутренний, чтобы классы вне вашей сборки не имели доступа.Затем вы можете использовать атрибут InternalsVisibleTo
, чтобы сделать специальное исключение для вашей сборки модульного тестирования, чтобы он мог видеть и использовать установщик.
Основным преимуществом этого подхода является то, что для получения тестируемого решения требуется меньше рефакторинга, но за счет "загрязнения" интерфейса вашего статического держателя Singleton.Поскольку в большинстве случаев синглтоны не нужны в первую очередь, я бы сначала подумал о рефакторинге для инжекции в конструктор.