web-dev-qa-db-fra.com

Exécutez le code une fois avant et après TOUS les tests dans xUnit.net

TL; DR - Je recherche l'équivalent xUnit du AssemblyInitialize de MSTest (c'est-à-dire la fonctionnalité ONE qu'il a que j'aime).

Plus précisément, je le recherche parce que j'ai des tests de fumée au sélénium que j'aimerais pouvoir exécuter sans autres dépendances. J'ai un appareil qui lancera IisExpress pour moi et le tuera lors de sa mise au rebut. Mais cela avant chaque test gonfle énormément le temps d'exécution.

Je voudrais déclencher ce code une fois au début des tests et le supprimer (arrêt du processus) à la fin. Comment pourrais-je m'y prendre?

Même si je peux obtenir un accès programmatique à quelque chose comme "combien de tests sont actuellement en cours d'exécution", je peux trouver quelque chose.

61
George Mauer

Depuis novembre 2015, xUnit 2 est sorti, il existe donc un moyen canonique de partager des fonctionnalités entre les tests. Il est documenté ici .

Fondamentalement, vous devrez créer une classe faisant le montage:

    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; }
    }

Une classe factice portant l'attribut CollectionDefinition. Cette classe permet à Xunit de créer une collection de tests et utilisera le fixture donné pour toutes les classes de tests de la collection.

    [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.
    }

Ensuite, vous devez ajouter le nom de la collection à toutes vos classes de test. Les classes de test peuvent recevoir le luminaire via le constructeur.

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

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

C'est un peu plus verbeux que MsTests AssemblyInitialize puisque vous devez déclarer à chaque classe de test à quelle collection de test il appartient, mais c'est aussi plus modulable (et avec MsTests vous devez toujours mettre une TestClass sur vos classes)

Remarque: les échantillons proviennent de la documentation .

42
gwenzek

Créez un champ statique et implémentez un finaliseur.

Vous pouvez utiliser le fait que xUnit crée un AppDomain pour exécuter votre assembly de test et le décharge lorsqu'il est terminé. Le déchargement du domaine d'application entraînera l'exécution du finaliseur.

J'utilise cette méthode pour démarrer et arrêter IISExpress.

public sealed class ExampleFixture
{
    public static ExampleFixture Current = new ExampleFixture();

    private ExampleFixture()
    {
        // Run at start
    }

    ~ExampleFixture()
    {
        Dispose();
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);

        // Run at end
    }        
}

Modifier: accédez au projecteur à l'aide de ExampleFixture.Current dans vos tests.

22
Jared Kells

Ce n'est pas possible de faire dans le cadre aujourd'hui. Il s'agit d'une fonctionnalité prévue pour 2.0.

Pour que cela fonctionne avant 2.0, il vous faudrait effectuer une ré-architecture significative sur le framework, ou écrire vos propres runners qui reconnaissent vos propres attributs spéciaux.

16
Brad Wilson

Pour exécuter du code lors de l'initialisation de l'assembly, alors on peut le faire (testé avec xUnit 2.3.1)

using Xunit.Abstractions;
using Xunit.Sdk;

[Assembly: Xunit.TestFramework("MyNamespace.MyClassName", "MyAssemblyName")]

namespace MyNamespace
{   
   public class MyClassName : XunitTestFramework
   {
      public MyClassName(IMessageSink messageSink)
        :base(messageSink)
      {
        // Place initialization code here
      }

      public new void Dispose()
      {
        // Place tear down code here
        base.Dispose();
      }
   }
}

Voir aussi https://github.com/xunit/samples.xunit/tree/master/AssemblyFixtureExample

9
Rolf Kristensen

J'utilise AssemblyFixture ( NuGet ).

Ce qu'il fait, c'est qu'il fournit un IAssemblyFixture<T> interface qui remplace toute IClassFixture<T> où vous souhaitez que la durée de vie de l'objet soit celle de l'assembly de test.

Exemple:

public class Singleton { }

public class TestClass1 : IAssemblyFixture<Singleton>
{
  readonly Singletone _Singletone;
  public TestClass1(Singleton singleton)
  {
    _Singleton = singleton;
  }

  [Fact]
  public void Test1()
  {
     //use singleton  
  }
}

public class TestClass2 : IAssemblyFixture<Singleton>
{
  readonly Singletone _Singletone;
  public TestClass2(Singleton singleton)
  {
    //same singleton instance of TestClass1
    _Singleton = singleton;
  }

  [Fact]
  public void Test2()
  {
     //use singleton  
  }
}
1
Shimmy

Votre outil de construction offre-t-il une telle fonctionnalité?

Dans le monde Java, lorsque vous utilisez Maven comme outil de construction, nous utilisons les phases du cycle de vie de la construction . Par exemple dans votre cas (tests d'acceptation avec des outils de type sélénium), on peut faire bon usage du pre-integration-test et post-integration-test phases pour démarrer/arrêter une webapp avant/après sa integration-tests.

Je suis sûr que le même mécanisme peut être mis en place dans votre environnement.

0
Vincent

Vous pouvez utiliser l'interface IUseFixture pour y arriver. De plus, tous vos tests doivent hériter de la classe TestBase. Vous pouvez également utiliser OneTimeFixture directement à partir de votre test.

public class TestBase : IUseFixture<OneTimeFixture<ApplicationFixture>>
{
    protected ApplicationFixture Application;

    public void SetFixture(OneTimeFixture<ApplicationFixture> data)
    {
        this.Application = data.Fixture;
    }
}

public class ApplicationFixture : IDisposable
{
    public ApplicationFixture()
    {
        // This code run only one time
    }

    public void Dispose()
    {
        // Here is run only one time too
    }
}

public class OneTimeFixture<TFixture> where TFixture : new()
{
    // This value does not share between each generic type
    private static readonly TFixture sharedFixture;

    static OneTimeFixture()
    {
        // Constructor will call one time for each generic type
        sharedFixture = new TFixture();
        var disposable = sharedFixture as IDisposable;
        if (disposable != null)
        {
            AppDomain.CurrentDomain.DomainUnload += (sender, args) => disposable.Dispose();
        }
    }

    public OneTimeFixture()
    {
        this.Fixture = sharedFixture;
    }

    public TFixture Fixture { get; private set; }
}

EDIT: corrige le problème créé par les nouveaux appareils pour chaque classe de test.

0
Khoa Le