Подтвердить что ты не робот

Как пробить статический синглтон?

У меня есть число классов, к которым меня попросили добавить некоторые модульные тесты с Rhino Mocks и некоторые проблемы.

Во-первых, я знаю, что RhinoMocks не позволяет насмехаться над участниками Static. Я ищу, какие у меня есть варианты (помимо использования TypeMock).

Пример класса я похож на приведенный ниже:

class Example1 : ISomeInterface
{
    private static ISomeInterface _instance;

    private Example1()
    {
        // set properties via private static methods
    }

    static Example1()
    {
        _instance = new Example1();
    }

    public static ISomeInterface Instance() 
    {
        get { return _instance; }
    }

    // Instance properties 

    // Other Instance Properties that represent objects that follow a similar pattern.
}

Итак, когда я вызываю класс выше, он выглядит примерно так...

Example1.Instance.SomeObject.GoDownARabbitHole();

Есть ли способ, чтобы я издевался над SomeObject.GoDownARabbitHole() в этой ситуации или издевался над экземпляром?

4b9b3361

Ответ 1

Синглтоны не согласуются с Testability, потому что их так трудно изменить. Вам было бы гораздо лучше использовать Injection Dependency, чтобы ввести экземпляр ISomeInterface в ваши классы потребления:

public class MyClass
{
    private readonly ISomeInterface dependency;

    public MyClass(ISomeInterface dependency)
    {
        if(dependency == null)
        {
            throw new ArgumentNullException("dependency");
        }

        this.dependency = dependency;
    }

    // use this.dependency in other members
}

Обратите внимание, что Guard Claus вместе с ключевым словом readonly гарантирует, что экземпляр ISomeInterface всегда будет доступен.

Это позволит вам использовать Rhino Mocks или другую динамическую макетную библиотеку, чтобы ввести Test Doubles в ISOMEInterface в классы-потребители.

Ответ 2

Не поощряя такие потоки, мне потребовалось некоторое время, чтобы заметить, что синглтоны не так сложно насмехаться. В конце концов, почему мы используем С#?

Просто используйте Reflection.

С предоставленным образцом кода вам нужно убедиться, что статический конструктор вызывается перед установкой статического поля в издеваемое изображение. В противном случае он может переписать ваш издеваемый объект. Просто назовите что-нибудь на синглете, которое не имеет эффекта до настройки теста.

ISomeInterface unused = Singleton.Instance();

System.Reflection.FieldInfo instance = typeof(Example1).GetField("_instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

Mock<ISomeInterface> mockSingleton = new Mock<ISomeInterface>();
instance.SetValue(null, mockSingleton.Object);

Я предоставил код для насмешек с Moq, но я думаю, Rhino Mocks очень похож.

Ответ 3

Здесь используется подход с низким уровнем касания, который использует делегат, который может быть установлен изначально и изменен во время выполнения. Это лучше объясняется примером (в частности, насмешкой DateTime.Now):

http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/11/09/systemtime-versus-isystemclock-dependencies-revisited.aspx

Ответ 4

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

Чтобы запустить код, содержащий синглтоны в тестовом жгуте, мы должны расслабить свойство 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 и установите его. Wed нравится писать код, подобный этому, в нашей тестовой настройке:

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

Ответ 5

Вы можете издеваться над интерфейсом ISomeInterface. Затем переформатируйте код, который использует его для использования инъекции зависимостей, чтобы получить ссылку на объект singleton. Я много раз сталкивался с этой проблемой в нашем коде, и мне нравится это решение наилучшим образом.

например:

public class UseTheSingleton
{
    private ISomeInterface myX;

    public UseTheSingleton(ISomeInterface x)
    {
        myX = x;
    }

    public void SomeMethod()
    {
        myX.
    }
}

Затем...

UseTheSingleton useIt = UseTheSingleton(Example1.Instance);

Ответ 6

Отъезд Инъекция зависимостей.

Вы уже начали это делать, но для тестирования тестов (статика и т.д.) вы можете использовать шаблон дизайна adapter для написания обертки вокруг этого сложного для тестирования кода. Используя interface этого адаптера, вы можете затем изолировать свой код.

Для любых советов по тестированию модулей и дальнейших проблем тестирования проверьте Блог Google Testing, в частности статьи Misko.

Instance

Вы говорите, что пишете тесты, поэтому может быть слишком поздно, но не могли бы вы реорганизовать статический экземпляр на экземпляр? Или есть ли настоящая причина, почему класс должен оставаться статичным?

Ответ 7

Вам не нужно исправлять все применения сразу, только тот, с которым вы имеете дело сейчас. Добавьте поле ISomeInterface в тестируемый класс и установите его через конструктор. Если вы используете Resharper (вы используете Resharper, не так ли?), Большая часть этого будет тривиальной. Если это действительно неудобно, вы можете иметь более одного конструктора, который устанавливает новое поле зависимостей, другое, которое вызывает первый из них с одиночным значением в качестве значения по умолчанию.