162 votes

xUnit.net : Installation globale + démontage ?

Cette question porte sur le cadre de test unitaire xUnit.net .

J'ai besoin d'exécuter un certain code avant l'exécution d'un test, ainsi qu'un certain code après l'exécution de tous les tests. J'ai pensé qu'il devrait y avoir une sorte d'attribut ou d'interface de marqueur pour indiquer le code d'initialisation globale et de terminaison, mais je ne les ai pas trouvés.

Alternativement, si j'invoque xUnit de manière programmatique, je peux également obtenir ce que je veux avec le code suivant :

static void Main()
{
    try
    {
        MyGlobalSetup();
        RunAllTests();  // What goes into this method?
    }
    finally
    {
        MyGlobalTeardown();
    }
}

Quelqu'un peut-il me donner un indice sur la manière d'exécuter, de manière déclarative ou programmatique, un code global de configuration et d'entretien ?

208voto

Erik Schierboom Points 6100

Pour autant que je sache, xUnit n'a pas de point d'extension global pour l'initialisation/le retard. Cependant, il est facile d'en créer un. Il suffit de créer une classe de test de base qui implémente IDisposable et faites votre initialisation dans le constructeur et votre démontage dans le IDisposable.Dispose méthode. Cela ressemblerait à ceci :

public abstract class TestsBase : IDisposable
{
    protected TestsBase()
    {
        // Do "global" initialization here; Called before every test method.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Called after every test method.
    }
}

public class DummyTests : TestsBase
{
    // Add test methods
}

Cependant, le code d'installation et de démontage de la classe de base sera exécuté pour chaque appel. Ce n'est peut-être pas ce que vous souhaitez, car ce n'est pas très efficace. Une version plus optimisée utiliserait la fonction IClassFixture<T> pour s'assurer que la fonctionnalité d'initialisation/de mise au rebut globale n'est appelée qu'une seule fois. Pour cette version, vous n'étendez pas une classe de base à partir de votre classe de test mais implémentez l'interface IClassFixture<T> l'interface où T fait référence à votre classe de fixation :

using Xunit;

public class TestsFixture : IDisposable
{
    public TestsFixture ()
    {
        // Do "global" initialization here; Only called once.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Only called once.
    }
}

public class DummyTests : IClassFixture<TestsFixture>
{
    public DummyTests(TestsFixture data)
    {
    }
}

Ce site se dans le constructeur de TestsFixture n'est exécuté qu'une seule fois pour chaque classe testée. Cela dépend donc de ce que vous voulez exactement pour choisir entre les deux méthodes.

68voto

Larry Smith Points 111

Je cherchais la même réponse, et pour le moment la documentation de xUnit est très utile en ce qui concerne la façon d'implémenter les Class Fixtures et les Collection Fixtures qui donnent aux développeurs une large gamme de fonctionnalités de mise en place/arrêt au niveau de la classe ou du groupe de classes. Ceci est en accord avec la réponse de Geir Sagberg, et donne une bonne implémentation squelette pour illustrer ce à quoi cela devrait ressembler.

https://xunit.net/docs/shared-context

Luminaires de collection Quand l'utiliser : lorsque vous voulez créer un contexte de test unique et le partager entre les tests de plusieurs classes de test, et le faire nettoyer après la fin de tous les tests des classes de test.

Parfois, vous voudrez partager un objet fixe entre plusieurs classes de test. L'exemple de la base de données utilisée pour les fixtures de classe est un excellent exemple : vous pouvez vouloir initialiser une base de données avec un ensemble de données de test, puis laisser ces données de test en place pour qu'elles soient utilisées par plusieurs classes de test. Vous pouvez utiliser la fonction de fixation de collection de xUnit.net pour partager une instance d'objet unique entre les tests de plusieurs classes de test.

Pour utiliser les dispositifs de collecte, vous devez suivre les étapes suivantes :

Créez la classe d'appareils, et placez le code de démarrage dans le constructeur de la classe d'appareils. Si la classe d'objets fixes doit effectuer un nettoyage, implémentez IDisposable sur la classe d'objets fixes et placez le code de nettoyage dans la méthode Dispose(). Créez la classe de définition de collection, en la décorant avec l'attribut [CollectionDefinition], en lui donnant un nom unique qui identifiera la collection de test. Ajoutez ICollectionFixture<> à la classe de définition de collection. Ajoutez l'attribut [Collection] à toutes les classes de test qui feront partie de la collection, en utilisant le nom unique que vous avez fourni à l'attribut [CollectionDefinition] de la classe de définition de collection de test. Si les classes de test ont besoin d'accéder à l'instance du dispositif, ajoutez-la en tant qu'argument du constructeur, et elle sera fournie automatiquement. Voici un exemple simple :

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");

        // ... initialize data in the test database ...
    }

    public void Dispose()
    {
        // ... clean up test data from the database ...
    }

    public SqlConnection Db { get; private set; }
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}

[Collection("Database collection")]
public class DatabaseTestClass1
{
    DatabaseFixture fixture;

    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

[Collection("Database collection")]
public class DatabaseTestClass2
{
    // ...
}

xUnit.net traite les objets fixes de collection de la même manière que les objets fixes de classe, sauf que la durée de vie d'un objet fixe de collection est plus longue : il est créé avant que les tests ne soient exécutés dans l'une des classes de test de la collection, et il ne sera pas nettoyé avant que toutes les classes de test de la collection aient fini de s'exécuter.

Les collections de tests peuvent également être décorées avec IClassFixture<>. xUnit.net traite cela comme si chaque classe de test individuelle dans la collection de tests était décorée avec le fixage de classe.

Les collections de tests influencent également la manière dont xUnit.net exécute les tests lorsqu'ils sont exécutés en parallèle. Pour plus d'informations, voir Exécution de tests en parallèle.

Remarque importante : les accessoires doivent se trouver dans le même montage que l'essai qui les utilise.

16voto

bradgonesurfing Points 8600

Il existe une solution simple et facile. Utilisez le plugin Fody.ModuleInit

https://github.com/Fody/ModuleInit

C'est un paquet nuget et lorsque vous l'installez, il ajoute un nouveau fichier appelé ModuleInitializer.cs au projet. Il y a une méthode statique qui est intégrée dans l'assemblage après la construction et qui est exécutée dès que l'assemblage est chargé et avant que quoi que ce soit ne soit exécuté.

Je l'utilise pour déverrouiller la licence du logiciel d'une bibliothèque que j'ai achetée. J'oubliais toujours de déverrouiller la licence dans chaque test et j'oubliais même de dériver le test d'une classe de base qui la déverrouillerait. Les brillantes étincelles qui ont écrit cette bibliothèque, au lieu de vous dire que la licence était verrouillée, ont introduit de subtiles erreurs numériques qui font que les tests échouent ou réussissent alors qu'ils ne devraient pas. Vous ne sauriez jamais si vous avez correctement déverrouillé la bibliothèque ou non. Donc maintenant mon module init ressemble à

/// <summary>
/// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded.
/// </summary>
public static class ModuleInitializer
{
    /// <summary>
    /// Initializes the module.
    /// </summary>
    public static void Initialize()
    {
            SomeLibrary.LicenceUtility.Unlock("XXXX-XXXX-XXXX-XXXX-XXXX");
    }
}

et tous les tests qui sont placés dans cet assemblage auront la licence débloquée correctement pour eux.

14voto

Geir Sagberg Points 436

Pour partager le code SetUp/TearDown entre plusieurs classes, vous pouvez utiliser l'outil xUnit CollectionFixture .

Citation :

Pour utiliser les dispositifs de collecte, vous devez suivre les étapes suivantes :

  • Créez la classe d'appareils, et mettez le code de démarrage dans le constructeur de la classe d'appareils.
  • Si la classe d'appareils doit effectuer un nettoyage, implémentez IDisposable sur la classe d'appareils, et placez le code de nettoyage dans la méthode Dispose() de la classe.
  • Créez la classe de définition de collection, en l'agrémentant de l'attribut [CollectionDefinition], en lui donnant un nom unique qui permettra de identifiera la collection de test.
  • Ajouter ICollectionFixture<> à la classe de définition de la collection.
  • Ajoutez l'attribut [Collection] à toutes les classes de test qui feront partie de la collection, en utilisant le nom unique que vous avez fourni à l'attribut [CollectionDefinition] de la classe de définition de la collection de test. de la classe de définition de la collection de tests.
  • Si les classes de test ont besoin d'accéder à l'instance du dispositif, ajoutez-la comme argument de constructeur, et elle sera fournie automatiquement.

7voto

TarmoPikaro Points 11

Si vous avez une initialisation globale et des fonctions de nettoyage globales, vous pouvez écrire la classe comme ceci :

[CollectionDefinition("TestEngine")]
public class TestEngineInitializer: IDisposable, ICollectionFixture<TestEngineInitializer>
{
    public TestEngineInitializer()
    {
        MyOwnTestEngine.Init();
    }

    public void Dispose()
    {
        MyOwnTestEngine.Cleanup();
    }
}

Et pour chaque classe de test, où cette initialisation doit être exécutée, vous devez ajouter un attribut supplémentaire :

[Collection("TestEngine")]
public class MyTests
{

Important : Les noms utilisés dans Collection y CollectionDefinition -Les attributs doivent correspondre.

Vous pouvez également fournir une instance de la classe TestEngine dans le constructeur, par exemple comme ceci :

[Collection("TestEngine")]
public class MyTests
{
     public MyTests(TestEngineInitializer initializer)
     {
     }

mais ce n'est pas obligatoire.

Toute la documentation sur ce sujet se trouve ici :

https://xunit.net/docs/shared-context

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