web-dev-qa-db-fra.com

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 de tester ses clients. Je ne pouvais pas immédiatement comprendre la raison sous-jacente. Quelqu'un peut-il expliquer ce qui rend impossible de se moquer d'une classe singleton? De plus, y a-t-il d'autres problèmes associés à la création d'un singleton de classe?

52
Aadith

Bien sûr, je pourrais écrire quelque chose comme n'utilisez pas singleton, ils sont mauvais, utilisez Guice/Spring/que ce soit mais d'abord, cela ne répondrait pas à votre question et deuxièmement, vous parfois doivent gérer le singleton, lors de l'utilisation de code hérité par exemple.

Donc, ne discutons pas du bon ou du mauvais à propos de singleton (il y a un autre question pour cela) mais voyons comment les gérer pendant les tests. Tout d'abord, regardons une implémentation commune du 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é, nous ne pouvons donc pas l'étendre (et nous ne pouvons pas contrôler la création d'instances dans les tests, mais c'est le but des singletons).

  2. Le getInstance est statique, il est donc difficile d'injecter un faux au lieu 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 consiste à rendre votre singleton "plus testable" en ajoutant un setter permettant de modifier le champ interne comme décrit dans Apprenez à arrêter de vous inquiéter et à aimer le singleton (vous n'ont même pas besoin d'un cadre moqueur dans ce cas). Si vous ne le faites pas, les frameworks de moqueries modernes basés sur les concepts d'interception et d'AOP permettent de surmonter les problèmes mentionnés précédemment.

Par exemple, Mocking Static Method Calls montre comment se moquer d'un Singleton en utilisant JMockit Expectations .

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

57
Pascal Thivent

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

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

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

Singleton.getInstance().doSometing();

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

@Inject private Singleton mySingleton;

Ensuite, lorsque vous testez les classes/composants/etc qui dépendent du singleton, vous pouvez facilement en injecter une version fictive.

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

L'utilisation des pratiques ci-dessus facilite le test unitaire de votre code et vous permet de vous concentrer sur votre logique fonctionnelle plutôt que sur la logique de câblage. Cela signifie également que votre code commence vraiment à devenir vraiment orienté objet, car toute utilisation de méthodes statiques (y compris les constructeurs) est discutablement procédurale. Ainsi, vos composants commencent également à devenir vraiment réutilisables.

Découvrez Google Guice en 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 IOC/DI trucs là-bas. :)

14
brindy

Un Singleton, par définition, a exactement une instance. Par conséquent, sa création est strictement contrôlée par la classe elle-même. Il s'agit généralement d'une classe concrète, pas d'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), vous ne pouvez donc pas facilement utiliser par exemple Injection de dépendances pour remplacer son "réelle" instance 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 simulations, vous auriez idéalement besoin d'une interface qui peut être librement sous-classée et dont l'implémentation concrète est fournie à ses clients par injection de dépendance.

Vous pouvez assouplir l'implémentation Singleton pour la rendre testable par

  • fournissant 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 rendre votre unité de code utilisant Singleton testable qu'en compromettant la "propreté" du Singleton. En fin de compte, il est préférable de ne pas l'utiliser du tout si vous pouvez l'éviter.

Mise à jour: Et voici la référence obligatoire à Working Effectively With Legacy Code par Michael Feathers.

13
Péter Török

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

  • faire une interface - SingletonInterface
  • rendre votre classe singleton implémenter cette interface
  • laisser Singleton.getInstance() retourner SingletonInterface
  • fournir une implémentation simulée de SingletonInterface dans vos tests
  • définissez-le dans le private static champ 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
Bozho

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

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

Au lieu de:

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

préférer:

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

Vous pouvez maintenant tester la fonction foo avec un objet bar simulé que vous pouvez contrôler. Vous avez supprimé la dépendance afin de pouvoir tester foo sans tester bar.

L'autre problème avec les singletons et les tests concerne le test du singleton lui-même. Un singleton est (de par sa conception) très difficile à reconstruire, par exemple, vous ne pouvez tester le constructeur de singleton qu'une seule fois. Il est également possible que l'instance unique de Bar conserve son état entre les tests, entraînant un succès ou un échec selon l'ordre d'exécution des tests.

3
Mark Byers

Il existe un moyen de se moquer de Singleton. Utilisez powermock pour simuler la méthode statique et utilisez Whitebox pour invoquer 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 au moment de l'exécution.

prendre plaisir

1
Moshe Tsabari