xUnit.net: Globales Setup + Teardown?

97

Diese Frage bezieht sich auf das Unit-Test-Framework xUnit.net .

Ich muss Code ausführen, bevor ein Test ausgeführt wird, und auch Code, nachdem alle Tests durchgeführt wurden. Ich dachte, es sollte eine Art Attribut- oder Markierungsschnittstelle geben, um den globalen Initialisierungs- und Beendigungscode anzuzeigen, konnte sie aber nicht finden.

Wenn ich xUnit programmgesteuert aufrufe, kann ich alternativ auch mit dem folgenden Code das erreichen, was ich will:

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

Kann mir jemand einen Hinweis geben, wie ein globaler Setup- / Teardown-Code deklarativ oder programmgesteuert ausgeführt werden kann?

Codismus
quelle
1
Ich denke hier ist die Antwort: stackoverflow.com/questions/12379949/…
the_joric

Antworten:

118

Soweit ich weiß, verfügt xUnit nicht über einen globalen Initialisierungs- / Teardown-Erweiterungspunkt. Es ist jedoch einfach, eine zu erstellen. Erstellen IDisposableSie einfach eine Basistestklasse, die Ihre Initialisierung im Konstruktor und Ihren Abbau in der IDisposable.DisposeMethode implementiert und durchführt . Das würde so aussehen:

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
}

Der Setup- und Teardown-Code für die Basisklasse wird jedoch für jeden Aufruf ausgeführt. Dies ist möglicherweise nicht das, was Sie möchten, da es nicht sehr effizient ist. Eine optimierte Version würde die IClassFixture<T>Schnittstelle verwenden, um sicherzustellen, dass die globale Initialisierungs- / Teardown-Funktionalität nur einmal aufgerufen wird. In dieser Version erweitern Sie keine Basisklasse aus Ihrer Testklasse, sondern implementieren die IClassFixture<T>Schnittstelle, die Tsich auf Ihre Fixture-Klasse bezieht:

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

Dies führt dazu, dass der Konstruktor TestsFixturefür jede zu testende Klasse nur einmal ausgeführt wird. Es kommt also darauf an, was genau Sie zwischen den beiden Methoden wählen möchten.

Erik Schierboom
quelle
4
Es scheint, dass IUseFixture nicht mehr existiert und durch IClassFixture ersetzt wurde.
GaTechThomas
9
Während dies funktioniert, denke ich, dass CollectionFixture in der Antwort von Geir Sagberg besser zu diesem Szenario passt, da es speziell für diesen Zweck entwickelt wurde. Sie müssen Ihre [Collection("<name>")]
Testklassen
8
Gibt es eine Möglichkeit zum asynchronen Einrichten und Herunterfahren?
Andrii
Es scheint, dass MS auch die IClassFixture-Lösung implementiert hat. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim
3
XUnit bietet drei Initialisierungsoptionen: pro Testmethode, pro Testklasse und über mehrere Testklassen hinweg. Die Dokumentation ist hier: xunit.net/docs/shared-context
GHN
48

Ich habe nach der gleichen Antwort gesucht, und zu diesem Zeitpunkt ist die xUnit-Dokumentation sehr hilfreich bei der Implementierung von Klassen- und Sammlungsvorrichtungen, die Entwicklern eine breite Palette von Einrichtungs- / Abbaufunktionen auf Klassen- oder Klassengruppenebene bieten. Dies steht im Einklang mit der Antwort von Geir Sagberg und bietet eine gute Skelettimplementierung, um zu veranschaulichen, wie es aussehen sollte.

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

Sammlungsvorrichtungen Verwendungszweck: Wenn Sie einen einzelnen Testkontext erstellen und ihn für Tests in mehreren Testklassen freigeben und nach Abschluss aller Tests in den Testklassen bereinigen möchten.

Manchmal möchten Sie ein Fixture-Objekt für mehrere Testklassen freigeben. Das für Klassenvorrichtungen verwendete Datenbankbeispiel ist ein gutes Beispiel: Möglicherweise möchten Sie eine Datenbank mit einer Reihe von Testdaten initialisieren und diese Testdaten dann für die Verwendung durch mehrere Testklassen beibehalten. Sie können die Collection Fixture-Funktion von xUnit.net verwenden, um eine einzelne Objektinstanz für Tests in mehreren Testklassen freizugeben.

Um Sammlungsvorrichtungen zu verwenden, müssen Sie die folgenden Schritte ausführen:

Erstellen Sie die Fixture-Klasse und fügen Sie den Startcode in den Fixture-Klassenkonstruktor ein. Wenn die Fixture-Klasse eine Bereinigung durchführen muss, implementieren Sie IDisposable für die Fixture-Klasse und fügen Sie den Bereinigungscode in die Dispose () -Methode ein. Erstellen Sie die Sammlungsdefinitionsklasse, indem Sie sie mit dem Attribut [CollectionDefinition] dekorieren und ihr einen eindeutigen Namen geben, der die Testsammlung identifiziert. Fügen Sie der Sammlungsdefinitionsklasse ICollectionFixture <> hinzu. Fügen Sie das Attribut [Sammlung] allen Testklassen hinzu, die Teil der Sammlung sein werden, und verwenden Sie dabei den eindeutigen Namen, den Sie für das Attribut [SammlungDefinition] der Definitionsklasse der Testsammlung angegeben haben. Wenn die Testklassen Zugriff auf die Fixture-Instanz benötigen, fügen Sie sie als Konstruktorargument hinzu, und sie wird automatisch bereitgestellt. Hier ist ein einfaches Beispiel:

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 behandelt Sammlungsvorrichtungen ähnlich wie Klassenvorrichtungen, mit der Ausnahme, dass die Lebensdauer eines Sammlungsvorrichtungsobjekts länger ist: Es wird erstellt, bevor Tests in einer der Testklassen in der Sammlung ausgeführt werden, und wird nicht bereinigt bis alle Testklassen in der Sammlung beendet sind.

Testsammlungen können auch mit IClassFixture <> dekoriert werden. xUnit.net behandelt dies so, als ob jede einzelne Testklasse in der Testsammlung mit dem Klassengerät dekoriert wäre.

Testsammlungen beeinflussen auch die Art und Weise, wie xUnit.net Tests ausführt, wenn sie parallel ausgeführt werden. Weitere Informationen finden Sie unter Paralleles Ausführen von Tests.

Wichtiger Hinweis: Die Geräte müssen sich in derselben Baugruppe befinden wie der Test, in dem sie verwendet werden.

Larry Smith
quelle
1
"Testsammlungen können auch mit IClassFixture <> dekoriert werden. XUnit.net behandelt dies so, als ob jede einzelne Testklasse in der Testsammlung mit dem Klassen-Fixture dekoriert wäre." Gibt es eine Chance, ein Beispiel dafür zu bekommen? Ich verstehe es nicht ganz.
RTF
@TannerFaulkner Das Klassen-Fixture war eine Möglichkeit, ein Setup und einen Abbau auf KLASSEN-Ebene durchzuführen, wie Sie es bei einem herkömmlichen .net-Unit-Test-Projekt erhalten, wenn Sie eine Testinitialisierungsmethode haben: [TestInitialize] public void Initialize () {
Larry Smith
Das einzige Problem, das ich damit habe, ist, dass Sie Ihre Testklassen mit dem CollectionAttribut dekorieren müssen, damit das "globale" Setup stattfinden kann. Das heißt, wenn Sie vor dem Ausführen von -any- test etwas einrichten möchten, müssen Sie -all- Testklassen mit diesem Attribut dekorieren. Dies ist meiner Meinung nach zu spröde, da das Vergessen, eine einzelne Testklasse zu dekorieren, zu Fehlern führen kann, die schwer zu finden sind. Es wäre schön, wenn xUnit einen Weg zu einem wirklich globalen Setup und Teardown schaffen würde.
Zodman
13

Es gibt eine einfache Lösung. Verwenden Sie das Fody.ModuleInit-Plugin

https://github.com/Fody/ModuleInit

Es ist ein Nuget-Paket und fügt bei der Installation eine neue Datei hinzu ModuleInitializer.cs, die dem Projekt aufgerufen wird . Hier gibt es eine statische Methode, die nach dem Erstellen in die Baugruppe eingewebt wird und ausgeführt wird, sobald die Baugruppe geladen wird und bevor etwas ausgeführt wird.

Ich verwende dies, um die Softwarelizenz für eine von mir gekaufte Bibliothek freizuschalten. Ich habe immer vergessen, die Lizenz in jedem Test freizuschalten und sogar zu vergessen, den Test von einer Basisklasse abzuleiten, die ihn entsperren würde. Die hellen Funken, die diese Bibliothek geschrieben haben, führten zu subtilen numerischen Fehlern, die dazu führten, dass Tests fehlschlugen oder bestanden wurden, wenn sie nicht sollten. Sie würden nie erfahren, ob Sie die Bibliothek korrekt entsperrt haben oder nicht. So sieht jetzt mein Modul init aus

/// <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");
    }
}

Bei allen Tests, die in diese Assembly gestellt werden, wird die Lizenz für sie ordnungsgemäß entsperrt.

Bradgonesurfing
quelle
2
Solide Idee; Leider scheint es bei DNX-Unit-Tests noch nicht zu funktionieren.
Jeff Dunlop
12

Um SetUp / TearDown-Code für mehrere Klassen freizugeben, können Sie das CollectionFixture von xUnit verwenden .

Zitat:

Um Sammlungsvorrichtungen zu verwenden, müssen Sie die folgenden Schritte ausführen:

  • Erstellen Sie die Fixture-Klasse und fügen Sie den Startcode in den Fixture-Klassenkonstruktor ein.
  • Wenn die Fixture-Klasse eine Bereinigung durchführen muss, implementieren Sie IDisposable für die Fixture-Klasse und fügen Sie den Bereinigungscode in die Dispose () -Methode ein.
  • Erstellen Sie die Sammlungsdefinitionsklasse, indem Sie sie mit dem Attribut [CollectionDefinition] dekorieren und ihr einen eindeutigen Namen geben, der die Testsammlung identifiziert.
  • Fügen Sie der Sammlungsdefinitionsklasse ICollectionFixture <> hinzu.
  • Fügen Sie das Attribut [Sammlung] allen Testklassen hinzu, die Teil der Sammlung sein werden, und verwenden Sie dabei den eindeutigen Namen, den Sie für das Attribut [SammlungDefinition] der Definitionsklasse der Testsammlung angegeben haben.
  • Wenn die Testklassen Zugriff auf die Fixture-Instanz benötigen, fügen Sie sie als Konstruktorargument hinzu, und sie wird automatisch bereitgestellt.
Geir Sagberg
quelle