web-dev-qa-db-fra.com

Tests unitaires avec singletons

J'ai préparé des tests automatiques avec le framework de test Visual Studio Team Edition. Je veux que l'un des tests se connecte à la base de données en suivant la procédure normale dans le 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 pouvez-vous contourner le problème singleton avec des tests unitaires?


Merci pour les réponses. Tous ont été très instructifs.

45
yeyeyerman
79
Gregory Pakosz

Vous pouvez utiliser l'injection de dépendance constructeur. 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 le test.

De plus, si vous utilisez le framework de test Visual Studio Team Edition, vous pouvez rendre le constructeur avec paramètre privé et tester la classe via l'accesseur.

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

Vous avez une classe qui dépend de 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 refactorisé pour prendre l'instance de Singleton comme paramètre 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 moquages ​​Moq est utilisée):

[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);
    ...
}
13
bniwredyc

Exemple tiré d'un livre : Travailler efficacement avec le code hérité

Également donné la même réponse ici: https://stackoverflow.com/a/28613595/929902

Pour exécuter du code contenant des singletons dans un faisceau de test, nous devons assouplir la propriété singleton. Voici comment nous procédons. La première étape consiste à ajouter une nouvelle méthode statique à la classe singleton. La 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 setter, nous pouvons créer une instance de test d'un PermitRepository et le définir. Nous aimerions écrire du 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);
}
9
Teoman shipahi

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

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

  1. Si votre Singleton possède un état global significatif, n'utilisez pas Singleton. Cela inclut le stockage persistant tel que les bases de données, les fichiers, etc.
  2. Dans les cas où la dépendance à un objet Singleton n'est pas évidente par le nom des classes, la dépendance doit être injectée. La nécessité d'injecter des instances Singleton dans les classes prouve une mauvaise utilisation du modèle (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 Singleton utilisent un mécanisme de chargement différé pour s'instancier. C'est trivial et leur cycle de vie ne changera probablement pas, sinon vous ne devriez pas utiliser Singleton.
5
Johannes Rudolph