xUnit.net: configurazione globale + smontaggio?


98

Questa domanda riguarda il framework di unit testing xUnit.net .

Devo eseguire del codice prima di eseguire qualsiasi test e anche del codice dopo che tutti i test sono stati eseguiti. Ho pensato che dovrebbe esserci un qualche tipo di interfaccia di attributo o indicatore per indicare il codice di inizializzazione e terminazione globale, ma non sono riuscito a trovarli.

In alternativa, se invoco xUnit a livello di codice, posso anche ottenere ciò che voglio con il seguente codice:

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

Qualcuno può fornirmi un suggerimento su come eseguire in modo dichiarativo o programmatico un codice di installazione / smontaggio globale?


1
Credo che qui è la risposta: stackoverflow.com/questions/12379949/...
the_joric

Risposte:


118

Per quanto ne so, xUnit non ha un punto di estensione di inizializzazione / smontaggio globale. Tuttavia, è facile crearne uno. Basta creare una classe di test di base che implementa IDisposableed esegue l'inizializzazione nel costruttore e lo smontaggio nel IDisposable.Disposemetodo. Questo sarebbe simile a questo:

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
}

Tuttavia, l'impostazione della classe di base e il codice di smontaggio verranno eseguiti per ogni chiamata. Potrebbe non essere quello che desideri, in quanto non è molto efficiente. Una versione più ottimizzata utilizzerebbe l' IClassFixture<T>interfaccia per garantire che la funzionalità di inizializzazione / smontaggio globale venga chiamata solo una volta. Per questa versione, non estendi una classe base dalla tua classe di test ma implementi l' IClassFixture<T>interfaccia dove si Triferisce alla tua classe 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)
    {
    }
}

Ciò risulterà nel costruttore di TestsFixtureessere eseguito solo una volta per ogni classe sotto test. Dipende quindi da cosa si vuole esattamente scegliere tra i due metodi.


4
Sembra che IUseFixture non esista più, essendo stato sostituito da IClassFixture.
GaTechThomas

9
Sebbene funzioni, penso che CollectionFixture nella risposta di Geir Sagberg si adatti meglio a questo scenario in quanto è stato progettato specificamente per questo scopo. Inoltre, non è necessario ereditare le classi di test, basta contrassegnarle con l' [Collection("<name>")]attributo
MichelZ

8
Esiste un modo per eseguire l'installazione e lo smontaggio asincrono?
Andrii

Sembra che anche MS abbia implementato la soluzione IClassFixture. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim

3
XUnit offre tre opzioni di inizializzazione: per metodo di test, per classe di test e comprende diverse classi di test. La documentazione è qui: xunit.net/docs/shared-context
GHN

48

Stavo cercando la stessa risposta, e in questo momento la documentazione di xUnit è molto utile per quanto riguarda come implementare Class Fixtures e Collection Fixtures che offrono agli sviluppatori un'ampia gamma di funzionalità di configurazione / smontaggio a livello di classe o gruppo di classi. Ciò è in linea con la risposta di Geir Sagberg e fornisce una buona implementazione dello scheletro per illustrare come dovrebbe essere.

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

Dispositivi di raccolta Quando utilizzarli: quando si desidera creare un singolo contesto di test e condividerlo tra i test in diverse classi di test e ripulirlo dopo che tutti i test nelle classi di test sono terminati.

A volte vorrete condividere un oggetto fixture tra più classi di test. L'esempio di database utilizzato per le fixture di classe è un ottimo esempio: potresti voler inizializzare un database con una serie di dati di test, quindi lasciare tali dati di test in posizione per l'uso da più classi di test. È possibile utilizzare la funzionalità del dispositivo di raccolta di xUnit.net per condividere una singola istanza di oggetto tra i test in diverse classi di test.

Per utilizzare i dispositivi di raccolta, è necessario eseguire i seguenti passaggi:

Creare la classe fixture e inserire il codice di avvio nel costruttore della classe fixture. Se la classe fixture deve eseguire la pulizia, implementare IDisposable sulla classe fixture e inserire il codice di pulizia nel metodo Dispose (). Creare la classe di definizione della raccolta, decorandola con l'attributo [CollectionDefinition], dandogli un nome univoco che identificherà la raccolta di prova. Aggiungi ICollectionFixture <> alla classe di definizione della raccolta. Aggiungere l'attributo [Collection] a tutte le classi di test che faranno parte della raccolta, utilizzando il nome univoco fornito all'attributo [CollectionDefinition] della classe di definizione della raccolta di test. Se le classi di test necessitano di accesso all'istanza del dispositivo, aggiungilo come argomento del costruttore e verrà fornito automaticamente. Qui c'è un semplice esempio:

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 tratta i dispositivi di raccolta più o meno allo stesso modo dei dispositivi di classe, tranne per il fatto che la durata di un oggetto dispositivo di raccolta è più lungo: viene creato prima che qualsiasi test venga eseguito in una qualsiasi delle classi di test nella raccolta e non verrà pulito fino al termine dell'esecuzione di tutte le classi di test nella raccolta.

Le raccolte di test possono anche essere decorate con IClassFixture <>. xUnit.net tratta questo come se ogni singola classe di test nella raccolta di test fosse decorata con il dispositivo di classe.

Le raccolte di test influenzano anche il modo in cui xUnit.net esegue i test quando vengono eseguiti in parallelo. Per ulteriori informazioni, vedere Esecuzione di test in parallelo.

Nota importante: i dispositivi devono trovarsi nello stesso assieme del test che li utilizza.


1
"Le raccolte di test possono anche essere decorate con IClassFixture <>. XUnit.net tratta questo come se ogni singola classe di test nella raccolta di test fosse decorata con il dispositivo di classe." C'è qualche possibilità che io possa avere un esempio di questo? Non lo capisco del tutto.
rtf

@TannerFaulkner L'apparecchiatura di classe era un modo per avere una configurazione a livello di CLASSE e smontaggio, come si ottiene con un tradizionale progetto di unit test .net quando si dispone di un metodo di inizializzazione del test: [TestInitialize] public void Initialize () {
Larry Smith,

L'unico problema che ho con questo è che devi decorare le tue classi di test con l' Collectionattributo affinché si verifichi l'impostazione "globale". Ciò significa che, se hai qualcosa che vuoi impostare prima che -any- venga eseguito, devi decorare le classi -all- test con questo attributo. Questo è troppo fragile secondo me, poiché dimenticare di decorare una singola classe di test può portare a errori difficili da rintracciare. Sarebbe bello se xUnit creasse un modo per un setup e uno smontaggio veramente globali.
Zodman

13

C'è una soluzione facile facile. Usa il plugin Fody.ModuleInit

https://github.com/Fody/ModuleInit

È un pacchetto nuget e quando lo installi aggiunge un nuovo file chiamato ModuleInitializer.csal progetto. C'è un metodo statico qui che viene intrecciato nell'assembly dopo la compilazione e viene eseguito non appena l'assembly viene caricato e prima che venga eseguito qualsiasi cosa.

Lo uso per sbloccare la licenza software per una libreria che ho acquistato. Mi sono sempre dimenticato di sbloccare la licenza in ogni test e persino di derivare il test da una classe base che lo avrebbe sbloccato. Le scintille luminose che hanno scritto questa libreria, invece di dirti che la licenza era bloccata, hanno introdotto sottili errori numerici che fanno sì che i test falliscano o passino quando non dovrebbero. Non sapresti mai se hai sbloccato correttamente la libreria o meno. Quindi ora il mio modulo init assomiglia

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

e tutti i test che vengono inseriti in questo assembly avranno la licenza sbloccata correttamente per loro.


2
Idea solida; sfortunatamente non sembra funzionare ancora con gli unit test DNX.
Jeff Dunlop

12

Per condividere il codice SetUp / TearDown tra più classi, puoi utilizzare CollectionFixture di xUnit .

Citazione:

Per utilizzare i dispositivi di raccolta, è necessario eseguire i seguenti passaggi:

  • Creare la classe fixture e inserire il codice di avvio nel costruttore della classe fixture.
  • Se la classe fixture deve eseguire la pulizia, implementare IDisposable sulla classe fixture e inserire il codice di pulizia nel metodo Dispose ().
  • Creare la classe di definizione della raccolta, decorandola con l'attributo [CollectionDefinition], dandogli un nome univoco che identificherà la raccolta di prova.
  • Aggiungi ICollectionFixture <> alla classe di definizione della raccolta.
  • Aggiungere l'attributo [Collection] a tutte le classi di test che faranno parte della raccolta, utilizzando il nome univoco fornito all'attributo [CollectionDefinition] della classe di definizione della raccolta di test.
  • Se le classi di test necessitano di accesso all'istanza del dispositivo, aggiungilo come argomento del costruttore e verrà fornito automaticamente.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.