насмешка singleton-класса

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

52
задан Bozho 20 February 2010 в 02:50
поделиться

5 ответов

Конечно, я мог бы написать что-нибудь вроде Не используйте синглтоны, они злые, используйте 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 является статическим, поэтому сложно вставить в код фальшивый объект вместо одноэлементного объекта , используя одноэлементный объект .

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

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

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

57
ответ дан 7 November 2019 в 09:21
поделиться

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

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

Вместо:

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

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

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

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

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

3
ответ дан 7 November 2019 в 09:21
поделиться

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

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

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

8
ответ дан 7 November 2019 в 09:21
поделиться

Синглтон, по определению, имеет ровно один экземпляр. Отсюда его создание строго контролируется самим классом. Обычно это конкретный класс, а не интерфейс, и благодаря своему частному конструктору он не является подклассом. Кроме того, он активно обнаруживается его клиентами (путем вызова Singleton.getInstance () или эквивалента), поэтому вы не можете легко использовать, например, Dependency Injection для замены его «реального» экземпляра макетным экземпляром:

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

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

-121--1040803-

userpath ('Очистить') должен сделать трюк.

-121--4293712-

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

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

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

Singleton.getInstance().doSometing();

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

@Inject private Singleton mySingleton;

Затем, когда вы пытаетесь протестировать класс/компоненты/etc, которые зависят от singleton вы можете легко ввести макет версии.

Большинство контейнеров для инъекций зависимостей позволяет пометить компонент как «singleton», но управлять этим зависит от контейнера.

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

Проверьте Google Guice в качестве стартера для 10:

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

Вы также можете посмотреть на весну и/или OSGi, которые могут сделать такого рода вещи. Там много вещей МОК/ДИ.:)

14
ответ дан 7 November 2019 в 09:21
поделиться

Синглтон, по определению, имеет ровно один экземпляр. Следовательно, его создание строго контролируется самим классом. Как правило, это конкретный класс, а не интерфейс, и из-за своего частного конструктора он не является подклассом. Более того, его активно находят клиенты (вызывая Singleton.getInstance() или эквивалент), поэтому вы не можете легко использовать, например, Dependency Injection для замены его "реального" экземпляра на имитатор:

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

Обновление: А вот и обязательная ссылка на Working Effectively With Legacy Code Майкла Фезерса.

13
ответ дан 7 November 2019 в 09:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: