Издевательский одноэлементный класс
Недавно я прочитал, что создание класса singleton делает невозможным издеваться над объектами класса, что затрудняет проверку его клиентов. Я не мог сразу понять причину. Может кто-нибудь объяснить, что делает невозможным издевательствовать один класс? Кроме того, есть ли еще проблемы, связанные с созданием класса singleton?
Ответы
Ответ 1
Конечно, я мог бы написать что-то вроде не использовать синглтон, они злые, использовать Guice/ Spring/все, но во-первых, это не ответит на ваш вопрос, а во-вторых, вы иногда должны работают с singleton, например, при использовании устаревшего кода.
Итак, давайте не обсуждать хорошее или плохое о singleton (для этого есть еще question), но давайте посмотрим, как с ними справиться во время тестирования. Во-первых, рассмотрим общую реализацию singleton:
public class Singleton {
private Singleton() { }
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public String getFoo() {
return "bar";
}
}
Здесь есть две проблемы с тестированием:
-
Конструктор является закрытым, поэтому мы не можем его расширять (и мы не можем управлять созданием экземпляров в тестах, но, ну, что точка одиночных чисел).
-
getInstance
является статическим, поэтому трудно вводить подделку вместо одиночного объекта в коде с помощью singleton.
Для издевательских фреймворков, основанных на наследовании и полиморфизме, обе точки, очевидно, являются большими проблемами. Если у вас есть контроль над кодом, один из вариантов - сделать ваш синглтон "более проверяемым", добавив сеттер, позволяющий настраивать внутреннее поле, как описано в Learn Остановить волнение и любить Синглтон (в этом случае вам даже не нужна насмешливая структура). Если вы этого не сделаете, современные издевательские фреймворки, основанные на перехвате и концепциях АОП, позволят преодолеть вышеупомянутые проблемы.
Например, Mocking Static Method Calls показывает, как издеваться над Singleton, используя Ожидания JMockit.
Другим вариантом было бы использовать PowerMock, расширение для Mockito или JMock, которое позволяет макетировать материал, обычно не вылепленный как статический, конечные, частные или конструкторские методы. Также вы можете получить доступ к внутренним элементам класса.
Ответ 2
Лучший способ издеваться над синглом - не использовать их вообще или, по крайней мере, не в традиционном смысле. Несколько способов, которые вы можете посмотреть, это:
- программирование для интерфейсов
- инъекция зависимостей
- инверсия управления
Итак, вместо того, чтобы иметь доступ к вам, выполните следующие действия:
Singleton.getInstance().doSometing();
... определите свой "singleton" как интерфейс и попробуйте что-то еще управлять жизненным циклом и введите его там, где он вам нужен, например, как переменная частного экземпляра:
@Inject private Singleton mySingleton;
Затем, когда вы тестируете модуль/компоненты/etc, которые зависят от singleton, вы можете легко ввести макетную версию.
Большинство контейнеров для инъекций зависимостей позволят вам пометить компонент как "singleton", но это до контейнера для управления этим.
Использование вышеприведенных методов упрощает unit test ваш код и позволяет вам сосредоточиться на своей функциональной логике вместо логики подключения. Это также означает, что ваш код действительно начинает стать объектно-ориентированным, поскольку любое использование статических методов (включая конструкторы) является дискуссионно процедурным. Таким образом, ваши компоненты начинают также стать действительно многоразовыми.
Проверьте Google Guice как стартер на 10:
http://code.google.com/p/google-guice/
Вы также можете посмотреть Spring и/или OSGi, которые могут делать такие вещи. Там много материалов IOC/DI.:)
Ответ 3
A Singleton, по определению, имеет ровно один экземпляр. Следовательно, его создание строго контролируется самим классом. Обычно это конкретный класс, а не интерфейс, и из-за его частного конструктора он не является подклассом. Более того, он активно используется его клиентами (путем вызова Singleton.getInstance()
или эквивалента), поэтому вы не можете легко использовать, например. Инъекция зависимостей, чтобы заменить свой "реальный" экземпляр макетным экземпляром:
class Singleton {
private static final myInstance = new Singleton();
public static Singleton getInstance () { return myInstance; }
private Singleton() { ... }
// public methods
}
class Client {
public doSomething() {
Singleton singleton = Singleton.getInstance();
// use the singleton
}
}
Для mocks вам в идеале нужен интерфейс, который может быть свободно подклассифицирован и чья конкретная реализация предоставляется его клиенту (-ам) путем инъекции зависимостей.
Вы можете ослабить реализацию Singleton, чтобы проверить ее на
- обеспечивающий интерфейс, который может быть реализован поддельным подклассом, а также "реальным".
- добавление метода
setInstance
для замены экземпляра в модульных тестах
Пример:
interface Singleton {
private static final myInstance;
public static Singleton getInstance() { return myInstance; }
public static void setInstance(Singleton newInstance) { myInstance = newInstance; }
// public method declarations
}
// Used in production
class RealSingleton implements Singleton {
// public methods
}
// Used in unit tests
class FakeSingleton implements Singleton {
// public methods
}
class ClientTest {
private Singleton testSingleton = new FakeSingleton();
@Test
public void test() {
Singleton.setSingleton(testSingleton);
client.doSomething();
// ...
}
}
Как вы видите, вы можете сделать только ваш блок с использованием Singleton-кода, поставив под угрозу "чистоту" Singleton. В конце концов, лучше не использовать его вообще, если вы можете его избежать.
Обновление: И вот обязательная ссылка на Эффективно работающая с устаревшим кодом от Michael Feathers.
Ответ 4
Это сильно зависит от реализации singleton. Но это в основном потому, что у него есть частный конструктор, и поэтому вы не можете его расширять. Но у вас есть следующий вариант
- создать интерфейс -
SingletonInterface
- Сделайте свой одноэлементный класс реализующим этот интерфейс
- let
Singleton.getInstance()
return SingletonInterface
- обеспечивает макетную реализацию
SingletonInterface
в ваших тестах
- установите его в поле
private static
на Singleton
с помощью отражения.
Но вам лучше избегать одиночных игр (которые представляют собой глобальное состояние). В этой лекции объясняются некоторые важные концепции дизайна с точки зрения проверки.
Ответ 5
Это не то, что синглтонская модель сама по себе является чистым злом, но она широко используется даже в ситуациях, когда она неадекватна. Многие разработчики думают: "О, мне, наверное, только когда-нибудь понадобится один из них, так что пусть это станет синглом". На самом деле вы должны думать: "Мне, вероятно, понадобится только один из них, поэтому позвольте построить его в начале моей программы и передать ссылки там, где это необходимо".
Первая проблема с singleton и тестированием - это не столько из-за синглтона, сколько из-за лени. Из-за удобства получения singleton зависимость от одноэлементного объекта часто внедряется непосредственно в методы, из-за чего очень сложно изменить singleton на другой объект с тем же интерфейсом, но с другой реализацией (например, макет объекта).
Вместо:
void foo() {
Bar bar = Bar.getInstance();
// etc...
}
предпочитают:
void foo(IBar bar) {
// etc...
}
Теперь вы можете протестировать функцию foo
с издеваемым объектом bar
, которым вы можете управлять. Вы удалили зависимость, чтобы вы могли протестировать foo
без тестирования bar
.
Другая проблема с синглонами и тестированием - это тестирование самого синглтона. Синглтон (по дизайну) очень трудно реконструировать, так что, например, вы можете только один раз проверить одиночный contructor. Также возможно, что единственный экземпляр bar
сохраняет состояние между тестами, что приводит к успеху или сбою в зависимости от порядка выполнения тестов.
Ответ 6
Есть способ издеваться над Синглтоном. Используйте powermock для моделирования статического метода и используйте Whitebox для вызова конструктора YourClass mockHelper = Whitebox
.invokeConstructor(YourClass.class);
Whitebox.setInternalState(mockHelper, "yourdata",mockedData);
PowerMockito.mockStatic(YourClass.class);
Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);
Что происходит, так это то, что байт-код Singleton изменяется во время выполнения.
наслаждайтесь