se moquer d'une classe singleton

J'ai récemment lu que faire un singleton de classe rend impossible de se moquer des objets de la classe, ce qui rend difficile le test de ses clients. Je ne pouvais pas comprendre immédiatement la raison sous-jacente. Quelqu'un peut-il expliquer ce qui rend impossible de se moquer d'une classe singleton? En outre, y a-t-il d'autres problèmes associés à la création d'un singleton de classe?

47
demandé sur Bozho 2010-02-20 15:37:24

6 réponses

Bien sûr, je pourrais écrire quelque chose comme n'utilisez pas singleton, ils sont mauvais, utilisez Guice / Spring / whatever mais d'abord, cela ne répondrait pas à votre question et deuxièmement, vous devez parfois traiter singleton, en utilisant du code hérité par exemple.

Donc, ne discutons pas du bien ou du mal à propos de singleton (il y a une autre question pour cela) mais voyons comment les gérer pendant les tests. Tout d'abord, regardons une mise en œuvre commune de l' 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";
    }
}

Il y a deux problèmes de test ici:

  1. Le constructeur est privé, donc nous ne pouvons pas l'étendre (et nous ne pouvons pas contrôler la création d'instances dans les tests mais, eh bien, c'est le point de singletons).

  2. Le getInstance est statique, il est donc difficile d'injecter un faux à la place de l'objet singleton dans le code en utilisant le singleton.

Pour les frameworks moqueurs basés sur l'héritage et le polymorphisme, les deux points sont évidemment de gros problèmes. Si vous avez le contrôle du code, une option est de rendre votre singleton "plus testable" en ajoutant un setter permettant de modifier le champ interne comme décrit dans apprendre à arrêter de S'inquiéter et aimer le Singleton (vous n'avez même pas besoin d'un cadre moqueur dans ce cas). Si vous ne le faites pas, les frameworks modernes basés sur les concepts d'interception et D'AOP permettent de surmonter les problèmes mentionnés précédemment.

Par exemple, méthode statique moqueuse Calls montre comment simuler un Singleton en utilisant Jmockit Expectations.

Une autre option serait d'utiliser PowerMock , une extension de Mockito ou JMock qui permet de simuler des choses normalement non moquables comme des méthodes statiques, finales, privées ou constructeurs. Vous pouvez également accéder aux internes d'une classe.

54
répondu Pascal Thivent 2017-05-23 11:54:54

La meilleure façon de se moquer d'un singleton est de ne pas les utiliser du tout, ou au moins pas dans le sens traditionnel. Quelques pratiques que vous voudrez peut-être Rechercher sont:

  • programmation aux interfaces
  • injection de dépendance
  • inversion du contrôle

Donc, plutôt que d'en avoir un seul, vous accédez comme ceci:

Singleton.getInstance().doSometing();

... définissez votre "singleton" en tant qu'interface et demandez à autre chose de gérer son cycle de vie et de l'injecter là où vous en avez besoin, par exemple en tant que privé variable d'instance:

@Inject private Singleton mySingleton;

Ensuite, lorsque vous testez la classe / components / etc qui dépend du singleton, vous pouvez facilement en injecter une version fictive.

La plupart des conteneurs d'injection de dépendance vous permettront de marquer un composant comme 'singleton', mais c'est au conteneur de le gérer.

En utilisant les pratiques ci-dessus, il est beaucoup plus facile de tester l'Unité de votre code et vous permet de vous concentrer sur votre logique fonctionnelle au lieu de la logique de câblage. Cela signifie également que votre code est vraiment commence à devenir vraiment orienté objet, car toute utilisation de méthodes statiques (y compris les constructeurs) est discutable. Ainsi, vos composants commencent à devenir vraiment réutilisables.

Consultez Google Guice comme entrée pour 10:

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

Vous pouvez également regarder Spring et / ou OSGi qui peuvent faire ce genre de chose. Il y a beaucoup de choses IOC / DI là-bas. :)

14
répondu brindy 2010-02-20 13:02:03

Un Singleton, par définition, a exactement une instance. D'où sa création est strictement contrôlée par la classe elle-même. Typiquement, c'est une classe concrète, pas une interface, et en raison de son constructeur privé, elle n'est pas sous-classable. De plus, il est trouvé activement par ses clients (en appelant Singleton.getInstance() ou un équivalent), de sorte que vous ne pouvez pas facilement utiliser par exemple Dependency Injection pour remplacer son instance "réelle" par une instance fictive:

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

Pour les mocks, vous auriez idéalement besoin d'une interface qui peut être librement sous-classé, et dont la mise en œuvre concrète est fournie à son(ses) client (s) par injection de dépendance.

Vous pouvez détendre L'implémentation Singleton pour la rendre testable par

  • fournir une interface qui peut être implémentée par une sous-classe fictive ainsi que la "vraie"
  • ajout d'une méthode setInstance pour permettre de remplacer l'instance dans les tests unitaires

Exemple:

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();
        // ...
    }
}

Comme vous le voyez, vous ne pouvez créer que votre unité de code Singleton testable en compromettant la "propreté" du Singleton. En fin de compte, il est préférable de ne pas utiliser si vous pouvez l'éviter.

Update: et voici la référence obligatoire à travailler efficacement avec le code hérité par Michael Feathers.

12
répondu Péter Török 2010-02-21 10:36:18

Cela dépend beaucoup de l'implémentation singleton. Mais surtout parce qu'il a un constructeur privé et donc vous ne pouvez pas l'étendre. Mais vous avez l'option suivante

  • créer une interface - SingletonInterface
  • faites en sorte que votre classe singleton implémente cette interface
  • let Singleton.getInstance() return SingletonInterface
  • fournissez une implémentation fictive de SingletonInterface dans vos tests
  • définissez - le dans le champ private static sur Singleton en utilisant la réflexion.

Mais vous feriez mieux d'éviter les singletons (qui représentent un état global). cette conférence explique certains concepts de conception importants du point de vue de la testabilité.

9
répondu Bozho 2010-02-20 12:46:54

Ce n'est pas que le modèle Singleton soit lui-même un mal pur, mais cela est massivement surutilisé même dans des situations où il est inapprorié. Beaucoup de développeurs pensent "Oh, je n'aurai probablement besoin que d'un seul d'entre eux, alors faisons-en un singleton". En fait, vous devriez penser "je n'aurai probablement besoin que d'un de ceux-ci, alors construisons-en un au début de mon programme et transmettons des références là où c'est nécessaire."

Le premier problème avec singleton et testing n'est pas tant à cause de la singleton mais à cause de la paresse. En raison de la commodité d'obtenir un singleton, la dépendance sur l'objet singleton est souvent intégrée directement dans les méthodes, ce qui rend très difficile de changer le singleton en un autre objet avec la même interface mais avec une implémentation différente (par exemple, un objet fictif).

Au Lieu de:

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

Préférer:

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

Maintenant, vous pouvez tester la fonction foo avec un moqué bar objet que vous pouvez contrôler. Vous avez retiré la dépendance afin que vous puissiez tester foo sans tester bar.

L'autre problème avec singletons et testing est lors du test du singleton lui-même. Un singleton est (par conception) très difficile à reconstruire, donc par exemple vous ne pouvez tester le constructeur singleton qu'une seule fois. Il est également possible que l'instance unique de Bar conserve l'état entre les tests, provoquant le succès ou l'échec en fonction de l'ordre dans lequel les tests sont exécutés.

3
répondu Mark Byers 2010-02-20 13:14:46

Il y a un moyen de se moquer de Singleton. Utilisez powermock pour simuler la méthode statique et utilisez Whitebox pour appeler le constructeur YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);

Ce qui se passe, c'est que le code D'octet Singleton change en cours d'exécution .

Profitez

1
répondu Moshe Tsabari 2014-12-09 15:55:30