издеваться над одиночным классом - PullRequest
51 голосов
/ 20 февраля 2010

Недавно я читал, что создание синглтона класса делает невозможным насмешку над объектами класса, что затрудняет тестирование его клиентов. Я не мог сразу понять основную причину. Может кто-нибудь объяснить, что делает насмешкой одноэлементный класс? Кроме того, есть ли еще проблемы, связанные с созданием синглтона класса?

Ответы [ 6 ]

56 голосов
/ 20 февраля 2010

Конечно, я мог бы написать что-то вроде не использовать синглтон, они злые, использовать Guice / Spring / что угодно но, во-первых, это не ответило бы на ваш вопрос, а во-вторых, вы иногда приходится иметь дело с синглтоном, например, при использовании устаревшего кода.

Итак, давайте не будем обсуждать хорошее или плохое в синглтоне (для этого есть еще один вопрос ), но давайте посмотрим, как с ними обращаться во время тестирования. Во-первых, давайте посмотрим на общую реализацию синглтона:

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

Здесь есть две проблемы тестирования:

  1. Конструктор является закрытым, поэтому мы не можем его расширять (и мы не можем контролировать создание экземпляров в тестах, но, в общем-то, в этом смысл синглетонов).

  2. * * * * * * * * * * * * * * * * * * * * * getInstance является статическим, поэтому трудно внедрить в код фальшивый объект вместо одноэлементного объекта , используя * .

Для фальшивых фреймворков, основанных на наследовании и полиморфизме, оба вопроса, очевидно, являются большими проблемами. Если у вас есть контроль над кодом, один из вариантов - сделать ваш синглтон «более тестируемым», добавив установщик, позволяющий настроить внутреннее поле, как описано в Научитесь прекращать беспокоиться и любить синглтон в этом случае даже не нужны насмешливые рамки). Если вы этого не сделаете, современные фреймворки, основанные на перехвате и концепциях АОП, позволяют преодолеть ранее упомянутые проблемы.

Например, Обработка вызовов статического метода показывает, как смоделировать синглтон, используя Ожидания JMockit .

Другой вариант - использовать PowerMock , расширение для Mockito или JMock, которое позволяет имитировать вещи, которые обычно не могут быть смоделированы, как статические, финальные, приватные или конструкторские методы. Также вы можете получить доступ к внутренним компонентам класса.

14 голосов
/ 20 февраля 2010

Лучший способ издеваться над синглтоном - не использовать их вообще или, по крайней мере, не в традиционном смысле. Вот несколько приемов, которые вы, возможно, захотите найти:

  • программирование для интерфейсов
  • Внедрение зависимости
  • инверсия управления

Так что вместо того, чтобы иметь сингл, вы получаете доступ к этому:

Singleton.getInstance().doSometing();

... определите ваш "синглтон" как интерфейс и попросите кого-то еще управлять его жизненным циклом и внедрять его там, где вам это нужно, например, как частную переменную экземпляра:

@Inject private Singleton mySingleton;

Затем, когда вы проводите модульное тестирование класса / компонентов / и т. Д., Которые зависят от синглтона, вы можете легко добавить имитационную версию.

Большинство контейнеров внедрения зависимостей позволяют пометить компонент как 'singleton', но это зависит от контейнера.

Использование описанных выше методов значительно упрощает модульное тестирование кода и позволяет сосредоточиться на функциональной логике, а не на логике подключения. Это также означает, что ваш код действительно начинает становиться действительно объектно-ориентированном, как и любым использование статических методов (в том числе и конструкторов) является спорным процедурным. Таким образом, ваши компоненты также становятся действительно многократно используемыми.

Проверьте Google Guice для начинающих на 10:

http://code.google.com/p/google-guice/

Вы также можете взглянуть на Spring и / или OSGi, которые могут делать подобные вещи. Есть много вещей IOC / DI там. :)

13 голосов
/ 20 февраля 2010

Синглтон, по определению, имеет ровно один экземпляр. Следовательно, его создание строго контролируется самим классом. Обычно это конкретный класс, а не интерфейс, и из-за его частного конструктора он не может быть разделен на подклассы. Более того, его активно находят клиенты (звоня по номеру 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
    }
}

Для макетов в идеале вам потребуется интерфейс, который может быть свободно разделен на подклассы и чья конкретная реализация предоставляется его клиенту (-ам) путем внедрения зависимостей.

Вы можете ослабить реализацию 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. В конце концов, лучше вообще не использовать его, если вы можете избежать этого.

Обновление: И вот обязательная ссылка на Эффективная работа с устаревшим кодом Майкла Фезерса.

9 голосов
/ 20 февраля 2010

Это очень сильно зависит от реализации синглтона. Но это в основном потому, что у него есть приватный конструктор, и, следовательно, вы не можете его расширить. Но у вас есть следующий вариант

  • сделать интерфейс - SingletonInterface
  • заставьте ваш синглтон-класс реализовать этот интерфейс
  • пусть Singleton.getInstance() возврат SingletonInterface
  • предоставляет ложную реализацию SingletonInterface в ваших тестах
  • установите его в поле private static на Singleton, используя отражение.

Но вам лучше избегать синглетонов (которые представляют глобальное состояние). Эта лекция объясняет некоторые важные концепции проектирования с точки зрения тестируемости.

3 голосов
/ 20 февраля 2010

Дело не в том, что паттерн Синглтон сам по себе является чистым злом, а в том, что он чрезмерно используется даже в ситуациях, когда он неуважительный. Многие разработчики думают: «О, возможно, мне когда-нибудь понадобится только один из них, поэтому давайте сделаем его синглтоном». На самом деле вы должны подумать: «Возможно, мне когда-нибудь понадобится только один из них, поэтому давайте создадим его в начале моей программы и передадим ссылки там, где это необходимо».

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

Вместо:

void foo() {
   Bar bar = Bar.getInstance();
   // etc...
}

предпочитают:

void foo(IBar bar) {
   // etc...
}

Теперь вы можете протестировать функцию foo с помощью смоделированного bar объекта, которым вы можете управлять. Вы удалили зависимость, чтобы можно было проверить foo без проверки bar.

Другая проблема с синглетонами и тестированием заключается в тестировании самого синглета. Синглтон (по замыслу) очень сложно реконструировать, поэтому, например, вы можете протестировать одноэлементный конструктор только один раз. Также возможно, что один экземпляр Bar сохраняет состояние между тестами, вызывая успех или неудачу в зависимости от порядка выполнения тестов.

1 голос
/ 09 декабря 2014

Есть способ издеваться над Синглтоном. Используйте powermock для макетирования статического метода и используйте Whitebox для вызова конструктора YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);

Что происходит, так это то, что байт-код Singleton изменяется во время выполнения.

наслаждайся

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...