J'ai lu récemment 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 comprendre immédiatement la raison sous-jacente. Quelqu'un peut-il s'il vous plaît 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?
Réponses
Trop de publicités?Bien sûr, je pourrais écrire quelque chose comme ne pas utiliser singleton, ils sont le mal, l'utilisation Guice/Printemps/whatever , mais première, ce ne serait pas répondre à votre question et la deuxième, parfois, vous avez à traiter avec singleton, lors de l'utilisation de l'héritage de code par exemple.
Donc, nous allons pas discuter des bonnes ou mauvaises à propos de singleton (il y a une autre question pour cela), mais nous allons voir comment les gérer pendant le test. Penchons-nous d'abord à une mise en œuvre 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:
Le constructeur est privé donc nous on ne peut pas l'étendre (et nous ne pouvons pas contrôler la création de cas de tests, mais, bien, c'est le point de singletons).
L'
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 se moquant des cadres fondé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 faire de votre singleton "plus testable" par l'ajout d'un setter permettant de modifier le champ interne tel que décrit dans Apprendre à Cesser de s'Inquiéter et à Aimer le Singleton (vous n'avez même pas besoin d'un moqueur cadre de cette affaire). Si vous ne le faites pas, moderne, se moquant des cadres fondés sur l'interception et de l'AOP concepts permettent de dépasser le mentionné précédemment problèmes.
Par exemple, se moquant de méthodes Statiques montre comment, à se moquer d'un Singleton à l'aide de JMockit Attentes.
Une autre option serait d'utiliser PowerMock, une extension de Mockito ou JMock qui permet de se moquer des trucs normalement pas mockable comme statique, finale, privé ou les méthodes constructeur. Vous pouvez également accéder à l'intérieur d'une classe.
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 examiner sont les suivantes:
- programmation d'interfaces
- l'injection de dépendance
- l'inversion de contrôle
Ainsi, plutôt que d'avoir un seul vous avez accès comme ceci:
Singleton.getInstance().doSometing();
... définir votre "singleton" comme une interface et quelque chose d'autre pour gérer le cycle de vie et l'injecter où vous en avez besoin, par exemple, à titre privé variable d'instance:
@Inject private Singleton mySingleton;
Puis, quand vous êtes de tests unitaires de la classe/composants/etc qui dépendent de l'singleton, vous pouvez facilement injecter une version fantaisie de lui.
La plupart d'injection de dépendance conteneurs vous permettra de marquer un composant comme "singleton", mais c'est le contenant pour le gérer.
En utilisant des pratiques ci-dessus, il est beaucoup plus facile de l'unité de tester votre code et vous permet de vous concentrer sur votre logique fonctionnelle au lieu de câblage logique. Cela signifie aussi que votre code commence vraiment à devenir vraiment Orienté Objet, toute utilisation de méthodes statiques (y compris les constructeurs) est discutable de procédure. Ainsi, vos composants commencent à devenir aussi vraiment réutilisable.
Découvrez Google Guice comme un démarreur pour 10:
http://code.google.com/p/google-guice/
Vous pouvez consulter également au Printemps et/ou d'OSGi qui peut faire ce genre de chose. Il y a beaucoup de CIO / DI choses là-bas. :)
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. Généralement, c'est une classe concrète, et non pas une interface, et en raison de son constructeur privé, il n'est pas subclassable. En outre, il est constaté activement par ses clients (en appelant Singleton.getInstance()
(ou équivalent), vous ne pouvez pas facilement utiliser, par exemple, l'Injection de Dépendance pour remplacer son "vrai" exemple avec un faux exemple:
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 se moque de vous, l'idéal serait besoin d'une interface qui peut être librement sous-classé, et dont la mise en œuvre concrète est à la disposition de ses client(s) par l'injection de dépendance.
Vous pourrez vous détendre le Singleton de mise en œuvre pour rendre testable par
- en fournissant une interface qui peut être mis en œuvre par une simulation de sous-classe ainsi que le "réel"
- l'ajout d'un
setInstance
méthode pour permettre le remplacement de 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 faire de votre Singleton-en utilisant le code d'unité vérifiable par compromettre la "propreté" du Singleton. En fin de compte, il est préférable de ne pas utiliser si vous pouvez l'éviter.
Mise à jour: Et voici le renvoi obligatoire pour Travailler Efficacement Avec le Code existant par Michael Plumes.
Cela dépend beaucoup de l'implémentation de singleton. Mais surtout parce qu'il a un constructeur privé et, par conséquent, vous ne pouvez pas le prolonger. Mais vous avez l'option suivante
- faire une interface -
SingletonInterface
- faites votre classe singleton à implémenter cette interface
- laissez -
Singleton.getInstance()
rendementSingletonInterface
- fournir une maquette de mise en œuvre de l'
SingletonInterface
dans vos tests - mettre dans l'
private static
champSingleton
l'aide de la réflexion.
Mais vous feriez mieux d'éviter les singletons (qui représentent un état global). Cette conférence explique certains des plus importants concepts de la conception de la testabilité du point de vue.
Ce n'est pas que le pattern Singleton est lui-même le mal pur, mais qui est massivement galvaudé, même dans les situations où il est inapproriate. De nombreux développeurs pensent "Oh, je vais probablement jamais besoin de l'un de ces donc, nous allons faire un singleton". En fait, vous devriez être en se disant "je vais probablement jamais besoin de l'un de ces, nous allons donc construire un, au début de mon programme et de passer des références là où c'est nécessaire."
Le premier problème avec singleton et le test n'est pas tellement à cause de l'singleton, mais en raison de la paresse. En raison de la possibilité d'obtenir un singleton, la dépendance de l'objet singleton est souvent incorporé directement dans les méthodes, ce qui rend très difficile de changer le singleton à un autre objet avec la même interface, mais avec une autre application (par exemple, un simulacre de l'objet).
Au lieu de:
void foo() {
Bar bar = Bar.getInstance();
// etc...
}
préférez:
void foo(IBar bar) {
// etc...
}
Vous pouvez maintenant tester la fonction foo
avec un moqué bar
objet que vous pouvez contrôler. Vous avez retiré la dépendance, de sorte que vous pouvez tester foo
sans test bar
.
L'autre problème avec les singletons et les tests est lors du test de l'singleton lui-même. Un singleton est (par conception) très difficile à reconstituer, par exemple, vous ne pouvez tester le singleton constructeur une fois. Il est également possible que la seule instance de Bar
conserve l'état entre les tests, causant de la réussite ou de l'échec selon l'ordre dans lequel les tests sont exécutés.