xUnit.net: configuration globale + démontage?

98

Cette question concerne le framework de test unitaire xUnit.net .

J'ai besoin d'exécuter du code avant l'exécution de tout test, ainsi que du code une fois tous les tests terminés. Je pensais qu'il devrait y avoir une sorte d'attribut ou d'interface de marqueur pour indiquer le code global d'initialisation et de terminaison, mais je n'ai pas pu les trouver.

Sinon, si j'appelle xUnit par programme, 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 façon d'exécuter de manière déclarative ou par programme un code de configuration / démontage global?

Codisme
la source
1
Je suppose que voici la réponse: stackoverflow.com/questions/12379949/…
the_joric

Réponses:

118

Autant que je sache, xUnit n'a pas de point d'extension global d'initialisation / démontage. Cependant, il est facile d'en créer un. Créez simplement une classe de test de base qui implémente IDisposableet effectuez votre initialisation dans le constructeur et votre démontage dans la IDisposable.Disposemé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, la configuration de la classe de base et le code de suppression seront exécutés pour chaque appel. Ce n'est peut-être pas ce que vous voulez, car ce n'est pas très efficace. Une version plus optimisée utiliserait l' IClassFixture<T>interface pour garantir que la fonctionnalité d'initialisation / de suppression 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' IClassFixture<T>interface où Tfait référence à votre classe de fixture:

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)
    {
    }
}

Cela se traduira par le constructeur de TestsFixturene être exécuté qu'une seule fois pour toutes les classes sous test. Cela dépend donc de ce que vous voulez exactement choisir entre les deux méthodes.

Erik Schierboom
la source
4
Il semble que IUseFixture n'existe plus, ayant été remplacé par IClassFixture.
GaTechThomas
9
Bien que cela fonctionne, je pense que CollectionFixture dans la réponse de Geir Sagberg convient mieux à ce scénario car il a été spécialement conçu à cet effet. Vous n'avez pas non plus à hériter de vos classes de test, vous les marquez simplement avec l' [Collection("<name>")]attribut
MichelZ
8
Existe-t-il un moyen de procéder à la configuration et au démontage asynchrones?
Andrii
Il semble que MS ait également implémenté la solution IClassFixture. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim
3
XUnit propose trois options d'initialisation: par méthode de test, par classe de test et couvrant plusieurs classes de test. La documentation est ici: xunit.net/docs/shared-context
GHN
48

Je cherchais la même réponse, et pour le moment, la documentation xUnit est très utile en ce qui concerne la façon d'implémenter les appareils de classe et les appareils de collection qui donnent aux développeurs un large éventail de fonctionnalités de configuration / démontage au niveau de la classe ou du groupe de classes. Ceci est conforme à la réponse de Geir Sagberg, et donne une bonne implémentation squelette pour illustrer à quoi cela devrait ressembler.

https://xunit.github.io/docs/shared-context.html

Fixtures de collection Quand 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 une fois tous les tests des classes de test terminés.

Parfois, vous souhaiterez partager un objet fixture entre plusieurs classes de test. L'exemple de base de données utilisé 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 une utilisation par plusieurs classes de test. Vous pouvez utiliser la fonction de fixation de collection de xUnit.net pour partager une seule instance d'objet entre les tests de plusieurs classes de test.

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

Créez la classe fixture et placez le code de démarrage dans le constructeur de classe fixture. Si la classe fixture doit effectuer un nettoyage, implémentez IDisposable sur la classe fixture 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 la collection de test. Si les classes de test ont besoin d'accéder à l'instance de fixture, ajoutez-la en tant qu'argument de 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 fixtures de collection de la même manière que les fixtures de classe, sauf que la durée de vie d'un objet de collection fixture 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 ne sera pas nettoyé jusqu'à ce que toutes les classes de test de la collection aient fini de s'exécuter.

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

Les collections de tests influencent également la manière dont xUnit.net exécute les tests lors de leur exécution en parallèle. Pour plus d'informations, consultez Exécution de tests en parallèle.

Remarque importante: les appareils doivent être dans le même assemblage que le test qui les utilise.

Larry Smith
la source
1
"Les collections de test peuvent également être décorées avec IClassFixture <>. XUnit.net traite cela comme si chaque classe de test individuelle de la collection de test était décorée avec le luminaire de classe." Est-ce que je pourrais avoir un exemple de cela? Je ne comprends pas très bien.
rtf le
@TannerFaulkner Le montage de classe était un moyen d'avoir une configuration et un démontage de niveau CLASS, comme vous obtenez avec un projet de test unitaire .net traditionnel lorsque vous avez une méthode Test Initialize: [TestInitialize] public void Initialize () {
Larry Smith
Le seul problème que j'ai avec ceci est que vous devez décorer vos classes de test avec l' Collectionattribut pour que la configuration "globale" se produise. Cela signifie que si vous avez quelque chose que vous voulez configurer avant que -any- test ne soit exécuté, vous devez décorer les classes -all- test avec cet attribut. C'est trop fragile à mon avis, car oublier de décorer une seule classe de test peut entraîner des erreurs difficiles à repérer. Ce serait bien si xUnit créait un moyen de configuration et de démontage vraiment globaux.
Zodman le
13

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

https://github.com/Fody/ModuleInit

C'est un package nuget et lorsque vous l'installez, il ajoute un nouveau fichier appelé ModuleInitializer.csau projet. Il existe une méthode statique ici qui est intégrée à l'assemblage après la génération 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 logicielle d'une bibliothèque que j'ai achetée. J'oubliais toujours de déverrouiller la licence à chaque test et même de dériver le test d'une classe de base qui la déverrouillerait. Les étincelles brillantes qui ont écrit cette bibliothèque, au lieu de vous dire qu'elle était verrouillée sous licence, ont introduit de subtiles erreurs numériques qui font échouer ou réussir les tests alors qu'ils ne le devraient pas. Vous ne saurez jamais si vous avez correctement déverrouillé la bibliothèque ou non. Alors 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 placés dans cet assemblage verront la licence déverrouillée correctement pour eux.

bradgonesurf
la source
2
Idée solide; malheureusement, il ne semble pas encore fonctionner avec les tests unitaires DNX.
Jeff Dunlop
12

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

Citation:

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

  • Créez la classe fixture et placez le code de démarrage dans le constructeur de classe fixture.
  • Si la classe fixture doit effectuer un nettoyage, implémentez IDisposable sur la classe fixture 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 la collection de test.
  • Si les classes de test ont besoin d'accéder à l'instance de fixture, ajoutez-la en tant qu'argument de constructeur, et elle sera fournie automatiquement.
Geir Sagberg
la source