54 votes

Tests unitaires avec singletons

J'ai préparé quelques tests automatiques avec le cadre de test de Visual Studio Team Edition. Je veux que l'un des tests se connecte à la base de données en suivant la procédure normale du programme :

string r_providerName = ConfigurationManager.ConnectionStrings["main_db"].ProviderName;

Mais je reçois une exception dans cette ligne. Je suppose que cela se produit parce que le ConfigurationManager est un singleton. Comment contourner le problème du singleton avec des tests unitaires ?


Merci pour les réponses. Elles ont toutes été très instructives.

1 votes

Quel est le message d'erreur exact ?

94voto

1 votes

Toutes ces références abordent le problème plus profondément que je ne pourrais le résumer dans une réponse. Elles sont vraiment excellentes.

2 votes

+1 ! Voir aussi le livre "Working Effectively with Legacy Code" de Michael Feathers ; il fournit des techniques pour tester avec les Singletons. amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/

1 votes

Il est particulièrement intéressant de constater que Singletons performants lien, conduisant à un 403 Interdit page d'erreur. C'est une façon assez subtile d'exprimer son rejet.

14voto

bniwredyc Points 4682

Vous pouvez utiliser l'injection de dépendance des constructeurs. Exemple :

public class SingletonDependedClass
{
    private string _ProviderName;

    public SingletonDependedClass()
        : this(ConfigurationManager.ConnectionStrings["main_db"].ProviderName)
    {
    }

    public SingletonDependedClass(string providerName)
    {
        _ProviderName = providerName;
    }
}

Cela vous permet de passer la chaîne de connexion directement à l'objet pendant les tests.

De même, si vous utilisez le cadre de test Visual Studio Team Edition, vous pouvez rendre le constructeur avec le paramètre privé et tester la classe par le biais de l'accesseur.

En fait, je résous ce genre de problèmes avec le mocking. Exemple :

Vous avez une classe qui dépend d'un singleton :

public class Singleton
{
    public virtual string SomeProperty { get; set; }

    private static Singleton _Instance;
    public static Singleton Insatnce
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = new Singleton();
            }

            return _Instance;
        }
    }

    protected Singleton()
    {
    }
}

public class SingletonDependedClass
{
    public void SomeMethod()
    {
        ...
        string str = Singleton.Insatnce.SomeProperty;
        ...
    }
}

Tout d'abord SingletonDependedClass doit être remanié pour prendre Singleton comme paramètre du constructeur :

public class SingletonDependedClass
{    
    private Singleton _SingletonInstance;

    public SingletonDependedClass()
        : this(Singleton.Insatnce)
    {
    }

    private SingletonDependedClass(Singleton singletonInstance)
    {
        _SingletonInstance = singletonInstance;
    }

    public void SomeMethod()
    {
        string str = _SingletonInstance.SomeProperty;
    }
}

Test de SingletonDependedClass ( Bibliothèque de simulateurs Moq est utilisé) :

[TestMethod()]
public void SomeMethodTest()
{
    var singletonMock = new Mock<Singleton>();
    singletonMock.Setup(s => s.SomeProperty).Returns("some test data");
    var target = new SingletonDependedClass_Accessor(singletonMock.Object);
    ...
}

10voto

Teoman shipahi Points 7988

Exemple tiré du livre : Travailler efficacement avec le code hérité

Même réponse ici : https://stackoverflow.com/a/28613595/929902

Pour exécuter du code contenant des singletons dans un harnais de test, nous devons relaxer la propriété singleton. Voici comment procéder. La première étape consiste à ajouter une nouvelle méthode statique à la classe singleton. Cette méthode nous permet de remplacer l'instance statique dans le singleton. Nous l'appellerons setTestingInstance .

public class PermitRepository
{
    private static PermitRepository instance = null;
    private PermitRepository() {}
    public static void setTestingInstance(PermitRepository newInstance)
    {
        instance = newInstance;
    }
    public static PermitRepository getInstance()
    {
        if (instance == null) {
            instance = new PermitRepository();
        }
        return instance;
    }
    public Permit findAssociatedPermit(PermitNotice notice) {
    ...
    }
    ...
}

Maintenant que nous avons ce paramètre, nous pouvons créer une instance de test d'un fichier PermitRepository et la définir. Nous aimerions écrire un code comme celui-ci dans notre configuration de test :

public void setUp() {
    PermitRepository repository = PermitRepository.getInstance();
    ...
    // add permits to the repository here
    ...
    PermitRepository.setTestingInstance(repository);
}

5voto

Johannes Rudolph Points 19845

Vous êtes confronté ici à un problème plus général. S'ils sont mal utilisés, les singletons nuisent à la testabilité.

J'ai fait un analyse détaillée de ce problème dans le contexte d'une conception découplée. Je vais essayer de résumer mes points :

  1. Si votre Singleton porte un état global significatif, n'utilisez pas Singleton. Cela inclut le stockage persistant comme les bases de données, les fichiers, etc.
  2. Dans les cas où la dépendance d'un objet singleton n'est pas évidente à partir du nom de la classe, la dépendance doit être injectée. La nécessité d'injecter des Instances Singleton dans les classes prouve une mauvaise utilisation du pattern (voir point 1).
  3. Le cycle de vie d'un Singleton est supposé être le même que celui de l'application. La plupart des implémentations de Singleton utilisent un mécanisme de chargement paresseux pour s'instancier. C'est trivial et il est peu probable que leur cycle de vie change, sinon vous ne devriez pas utiliser de Singleton.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X