Question

J'écris une série de classes de collection en C #, chacune implémentant des interfaces personnalisées similaires.Est-il possible d'écrire une seule collection de tests unitaires pour une interface et de les exécuter automatiquement sur plusieurs implémentations différentes?Je voudrais éviter tout code de test dupliqué pour chaque implémentation.

Je suis prêt à examiner n'importe quel framework (NUnit, etc.) ou extension Visual Studio pour accomplir cela.


Pour ceux qui cherchent à faire de même, j'ai publié ma solution concrète, basée sur la solution acceptée par avandeursen , comme une réponse .

Était-ce utile?

La solution

Oui, c'est possible. L'astuce consiste à laisser votre hiérarchie de test de classes unitaires suivre la hiérarchie de classes de votre code.

Supposons que vous ayez une interface Itf avec l'implémentation des classes C1 et C2.

Vous créez d'abord une classe de test pour Itf (ItfTest). Pour effectuer réellement le test, vous devez créer une implémentation fictive de votre interface Itf.

Tous les tests de ce ItfTest devraient passer sur n'importe quelle implémentation de Itf (!). Sinon, votre implémentation n'est pas conforme au principe de substitution de Liskov (le «L» dans le SOLID principes de conception OO)

Ainsi, pour créer un cas de test pour C1, votre classe C1Test peut étendre ItfTest. Votre extension doit remplacer la création d'objet fictif par la création d'un objet C1 (en l'injectant ou en utilisant un GoF méthode d'usine ). De cette manière, tous les cas ItfTest sont appliqués aux instances de type C1. De plus, votre classe C1Test peut contenir des cas de test supplémentaires spécifiques à C1.

De même pour C2. Et vous pouvez répéter l'astuce pour des classes et des interfaces imbriquées plus profondes.

Références: Binder's modèle Polymorphic Server Test , et PACT de McGregor - Architecture parallèle pour les tests de composants.

Autres conseils

En développant la réponse Joe's , vous pouvez utiliser l'attribut [TestCaseSource] dans NUnit de la même manière que MBUnitRowTest.Vous pouvez créer une source de cas de test avec les noms de vos classes.Vous pouvez ensuite décorer chaque test avec l'attribut TestCaseSource.Ensuite, en utilisant Activator.CreateInstance, vous pouvez effectuer un cast vers l'interface et vous serez défini.

Quelque chose comme ça (note - tête compilée)

string[] MyClassNameList = { "Class1", "Class2" };

[TestCaseSource(MyClassNameList)]
public void Test1(string className)
{
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface;

    ...
}

Pour ce faire, vous pouvez utiliser les attributs [RowTest] de MBUnit .L'exemple ci-dessous montre où vous transmettez à la méthode une chaîne pour indiquer la classe d'implémentation d'interface que vous souhaitez instancier, puis crée cette classe via la réflexion:

[RowTest]
[Row("Class1")]
[Row("Class2")]
[Row("Class3")]
public void TestMethod(string type)
{
   IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface;

   //Do tests on foo:
}

Dans les attributs [Row], vous pouvez transmettre n'importe quel nombre arbitraire de paramètres d'entrée, tels que les valeurs d'entrée pour les tests ou les valeurs attendues à renvoyer par les appels de méthode.Vous devrez ajouter des arguments des types correspondants comme arguments d'entrée de méthode de test.

Voici mon implémentation concrète basée sur la réponse d'avandeursen :

[TestClass]
public abstract class IMyInterfaceTests
{
    protected abstract IMyInterface CreateInstance();

    [TestMethod]
    public void SomeTest()
    {
        IMyInterface instance = CreateInstance();
        // Run the test
    }
}

Chaque implémentation d'interface définit ensuite la classe de test suivante:

[TestClass]
public class MyImplementationTests : IMyInterfaceTests
{
    protected override IMyInterface CreateInstance()
    {
        return new MyImplementation();
    }
}

SomeTest est exécuté une fois pour chaque TestClass concret dérivé de IMyInterfaceTests.En utilisant une classe de base abstraite, j'évite le besoin de toute implémentation fictive.Assurez-vous d'ajouter TestClassAttribute aux deux classes ou cela ne fonctionnera pas.Enfin, vous pouvez ajouter des tests spécifiques à l'implémentation (tels que des constructeurs) à la classe enfant si vous le souhaitez.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top