xUnit.net: Globalna konfiguracja + porzucenie?

102

To pytanie dotyczy frameworka testów jednostkowych xUnit.net .

Muszę uruchomić kod przed wykonaniem jakiegokolwiek testu, a także kod po wykonaniu wszystkich testów. Pomyślałem, że powinien istnieć jakiś interfejs atrybutów lub znaczników wskazujący globalny kod inicjalizacji i zakończenia, ale nie mogłem ich znaleźć.

Alternatywnie, jeśli programowo wywołuję xUnit, mogę również osiągnąć to, co chcę, za pomocą następującego kodu:

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

Czy ktoś może mi podpowiedzieć, jak deklaratywnie lub programowo uruchomić globalny kod konfiguracji / dezaktywacji?

Kodyzm
źródło
1
Myślę, że tutaj jest odpowiedź: stackoverflow.com/questions/12379949/ ...
the_joric

Odpowiedzi:

122

O ile wiem, xUnit nie ma globalnego punktu rozszerzenia inicjalizacji / zakończenia. Jednak łatwo jest go utworzyć. Po prostu utwórz podstawową klasę testową, która implementuje IDisposablei wykonaj inicjalizację w konstruktorze oraz rozerwanie w IDisposable.Disposemetodzie. Wyglądałoby to tak:

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
}

Jednak konfiguracja klasy bazowej i kod dezaktywacji będą wykonywane dla każdego wywołania. Może to nie być to, czego chcesz, ponieważ nie jest to zbyt wydajne. Bardziej zoptymalizowana wersja korzystałaby z IClassFixture<T>interfejsu w celu zapewnienia, że ​​funkcja globalnej inicjalizacji / dezaktywacji jest wywoływana tylko raz. W tej wersji nie rozszerzasz klasy bazowej z klasy testowej, ale implementujesz IClassFixture<T>interfejs, w którym Todnosi się do Twojej klasy urządzeń:

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

Spowoduje to, że konstruktor TestsFixturebędzie uruchamiany tylko raz dla każdej testowanej klasy. Zależy to zatem od tego, co dokładnie chcesz wybrać między tymi dwiema metodami.

Erik Schierboom
źródło
4
Wygląda na to, że IUseFixture już nie istnieje, został zastąpiony przez IClassFixture.
GaTechThomas,
9
Chociaż to działa, myślę, że CollectionFixture w odpowiedzi od Geira Sagberga lepiej pasuje do tego scenariusza, ponieważ została specjalnie zaprojektowana do tego celu. Nie musisz też dziedziczyć klas testowych, po prostu [Collection("<name>")]
oznaczasz
9
Czy jest jakiś sposób na skonfigurowanie i przerwanie konfiguracji asynchronicznej?
Andrii
Wygląda na to, że firma MS zaimplementowała również rozwiązanie IClassFixture. docs.microsoft.com/en-us/aspnet/core/test/ ...
lbrahim,
3
XUnit oferuje trzy opcje inicjalizacji: na metodę testową, na klasę testową i obejmującą kilka klas testowych. Dokumentacja jest tutaj: xunit.net/docs/shared-context
GHN
50

Szukałem tej samej odpowiedzi i w tej chwili dokumentacja xUnit jest bardzo pomocna w odniesieniu do tego, jak zaimplementować urządzenia klasowe i kolekcje, które zapewniają programistom szeroki zakres funkcji konfiguracji / rozłączania na poziomie klasy lub grupy klas. Jest to zgodne z odpowiedzią Geira Sagberga i zapewnia dobrą implementację szkieletu, aby zilustrować, jak powinien wyglądać.

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

Collection Fixtures Kiedy używać: gdy chcesz utworzyć pojedynczy kontekst testowy i udostępnić go między testami w kilku klasach testowych oraz wyczyścić go po zakończeniu wszystkich testów w klasach testowych.

Czasami będziesz chciał udostępnić obiekt urządzenia wielu klasom testowym. Przykład bazy danych używany dla urządzeń klasowych jest świetnym przykładem: możesz chcieć zainicjować bazę danych z zestawem danych testowych, a następnie pozostawić te dane testowe na miejscu do użytku przez wiele klas testowych. Możesz użyć funkcji gromadzenia urządzeń xUnit.net, aby udostępnić jedną instancję obiektu testom w kilku klasach testowych.

Aby użyć osprzętu do kolekcjonowania, musisz wykonać następujące czynności:

Utwórz klasę fixture i umieść kod startowy w konstruktorze klasy fixture. Jeśli klasa fixture wymaga czyszczenia, zaimplementuj IDisposable w klasie fixture i umieść kod cleanup w metodzie Dispose (). Utwórz klasę definicji kolekcji, dekorując ją atrybutem [CollectionDefinition], nadając jej unikalną nazwę, która będzie identyfikować kolekcję testową. Dodaj ICollectionFixture <> do klasy definicji kolekcji. Dodaj atrybut [Collection] do wszystkich klas testowych, które będą częścią kolekcji, używając unikatowej nazwy podanej w atrybucie [CollectionDefinition] klasy definicji kolekcji testów. Jeśli klasy testowe potrzebują dostępu do instancji fixture, dodaj ją jako argument konstruktora, a zostanie ona dostarczona automatycznie. Oto prosty przykład:

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 traktuje urządzenia kolekcji w taki sam sposób, jak urządzenia klas, z tym wyjątkiem, że czas życia obiektu ustalania kolekcji jest dłuższy: jest tworzony przed uruchomieniem jakichkolwiek testów w którejkolwiek z klas testowych w kolekcji i nie zostanie wyczyszczony aż do zakończenia działania wszystkich klas testowych w kolekcji.

Kolekcje testowe można również ozdobić za pomocą IClassFixture <>. xUnit.net traktuje to tak, jakby każda pojedyncza klasa testowa w kolekcji testów była ozdobiona urządzeniem klasy.

Kolekcje testów wpływają również na sposób, w jaki xUnit.net uruchamia testy podczas równoległego ich uruchamiania. Aby uzyskać więcej informacji, zobacz równoległe uruchamianie testów.

Ważna uwaga: Oprawy muszą być w tym samym zespole, co test, który ich używa.

Larry Smith
źródło
1
„Kolekcje testowe można również ozdobić za pomocą IClassFixture <>. XUnit.net traktuje to tak, jakby każda klasa testowa z kolekcji testowej była ozdobiona osprzętem klas”. Jest jakaś szansa, żebym miał na to przykład? Nie całkiem to rozumiem.
rtf
@TannerFaulkner Urządzenie klasy było sposobem na skonfigurowanie poziomu KLASY i porzucenie go, tak jak w przypadku tradycyjnego projektu testów jednostkowych .net, gdy masz metodę inicjalizacji testu: [TestInitialize] public void Initialize () {
Larry Smith
Jedynym problemem, jaki mam z tym, jest to, że musisz ozdobić swoje klasy testowe Collectionatrybutem, aby wystąpiła konfiguracja „globalna”. Oznacza to, że jeśli masz coś, co chcesz skonfigurować przed uruchomieniem -any- test, musisz ozdobić -all- klasy testowe tym atrybutem. Moim zdaniem jest to zbyt kruche, ponieważ zapomnienie o udekorowaniu jednej klasy testowej może prowadzić do błędów, które są trudne do wyśledzenia. Byłoby miło, gdyby xUnit stworzył sposób na prawdziwie globalną konfigurację i porzucenie.
Zodman
13

Jest proste, łatwe rozwiązanie. Użyj wtyczki Fody.ModuleInit

https://github.com/Fody/ModuleInit

Jest to pakiet nuget i po zainstalowaniu dodaje nowy plik wywoływany ModuleInitializer.csdo projektu. Jest tutaj jedna metoda statyczna, która jest wklejana do zestawu po kompilacji i jest uruchamiana zaraz po załadowaniu zestawu i przed uruchomieniem.

Używam tego do odblokowania licencji oprogramowania do zakupionej biblioteki. Zawsze zapominałem o odblokowaniu licencji w każdym teście, a nawet zapominałem o wyprowadzeniu testu z klasy bazowej, która ją odblokuje. Jasne iskry, które napisały tę bibliotekę, zamiast powiedzieć, że licencja została zablokowana, wprowadziły subtelne błędy numeryczne, które powodują, że testy kończą się niepowodzeniem lub zaliczają się, gdy nie powinny. Nigdy nie wiesz, czy poprawnie odblokowałeś bibliotekę, czy nie. Więc teraz wygląda jak inicjalizacja mojego modułu

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

a wszystkie testy, które są umieszczane w tym zestawie, będą miały poprawnie odblokowaną licencję.

bradgonesurfing
źródło
2
Solidny pomysł; niestety wydaje się, że nie działa jeszcze z testami jednostkowymi DNX.
Jeff Dunlop,
12

Aby udostępnić kod SetUp / TearDown między wieloma klasami, możesz użyć CollectionFixture xUnit .

Zacytować:

Aby użyć osprzętu do kolekcjonowania, musisz wykonać następujące czynności:

  • Utwórz klasę fixture i umieść kod startowy w konstruktorze klasy fixture.
  • Jeśli klasa fixture wymaga czyszczenia, zaimplementuj IDisposable w klasie fixture i umieść kod cleanup w metodzie Dispose ().
  • Utwórz klasę definicji kolekcji, dekorując ją atrybutem [CollectionDefinition], nadając jej unikalną nazwę, która będzie identyfikować kolekcję testową.
  • Dodaj ICollectionFixture <> do klasy definicji kolekcji.
  • Dodaj atrybut [Collection] do wszystkich klas testowych, które będą częścią kolekcji, używając unikatowej nazwy podanej w atrybucie [CollectionDefinition] klasy definicji kolekcji testów.
  • Jeśli klasy testowe potrzebują dostępu do instancji fixture, dodaj ją jako argument konstruktora, a zostanie ona dostarczona automatycznie.
Geir Sagberg
źródło