Eseguire unit test in serie (anziché in parallelo)


96

Sto tentando di testare l'unità di un motore di gestione host WCF che ho scritto. Il motore fondamentalmente crea istanze ServiceHost al volo in base alla configurazione. Questo ci consente di riconfigurare dinamicamente quali servizi sono disponibili senza doverli disattivare tutti e riavviarli ogni volta che viene aggiunto un nuovo servizio o viene rimosso uno vecchio.

Tuttavia, ho riscontrato difficoltà nel testare l'unità di questo motore di gestione host, a causa del modo in cui funziona ServiceHost. Se un ServiceHost è già stato creato, aperto e non ancora chiuso per un particolare endpoint, non è possibile creare un altro ServiceHost per lo stesso endpoint, con conseguente eccezione. A causa del fatto che le moderne piattaforme di unit test parallelizzano la loro esecuzione dei test, non ho un modo efficace per unit test di questo pezzo di codice.

Ho usato xUnit.NET, sperando che a causa della sua estensibilità, avrei potuto trovare un modo per costringerlo a eseguire i test in serie. Tuttavia, non ho avuto fortuna. Spero che qualcuno qui su SO abbia riscontrato un problema simile e sappia come eseguire i test unitari in serie.

NOTA: ServiceHost è una classe WCF, scritta da Microsoft. Non ho la capacità di cambiare il suo comportamento. Anche l'hosting di ciascun endpoint del servizio una sola volta è il comportamento corretto ... tuttavia, non è particolarmente favorevole ai test di unità.


Questo particolare comportamento di ServiceHost non sarebbe qualcosa che potresti voler affrontare?
Robert Harvey

ServiceHost è scritto da Microsoft. Non ho alcun controllo su di esso. E tecnicamente parlando, è un comportamento valido ... non dovresti mai avere più di un ServiceHost per endpoint.
jrista

1
Ho avuto un problema simile cercando di eseguire più file TestServerin finestra mobile. Quindi ho dovuto serializzare i test di integrazione.
h-rai

Risposte:


114

Ogni classe di test è una raccolta di test univoca e i test sotto di essa verranno eseguiti in sequenza, quindi se metti tutti i tuoi test nella stessa raccolta, verranno eseguiti in sequenza.

In xUnit puoi apportare le seguenti modifiche per ottenere ciò:

Di seguito verrà eseguito in parallelo:

namespace IntegrationTests
{
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

Per renderlo sequenziale devi solo mettere entrambe le classi di test nella stessa raccolta:

namespace IntegrationTests
{
    [Collection("Sequential")]
    public class Class1
    {
        [Fact]
        public void Test1()
        {
            Console.WriteLine("Test1 called");
        }

        [Fact]
        public void Test2()
        {
            Console.WriteLine("Test2 called");
        }
    }

    [Collection("Sequential")]
    public class Class2
    {
        [Fact]
        public void Test3()
        {
            Console.WriteLine("Test3 called");
        }

        [Fact]
        public void Test4()
        {
            Console.WriteLine("Test4 called");
        }
    }
}

Per maggiori info puoi fare riferimento a questo link


23
Risposta sottovalutata, credo. Sembra funzionare e mi piace la granularità, poiché ho test parallelizzabili e non parallelizzabili in un unico assieme.
Igand

1
Questo è il modo corretto per farlo, rifare la documentazione di Xunit.
Håkon K. Olafsen

2
Questa dovrebbe essere la risposta accettata perché in genere alcuni test possono essere eseguiti in parallelo (nel mio caso tutti gli unit test), ma alcuni falliscono in modo casuale quando vengono eseguiti in parallelo (nel mio caso quelli che utilizzano client / server Web in memoria), quindi uno è in grado di ottimizzare l'esecuzione dei test se lo si desidera.
Alexei

2
Questo non ha funzionato per me in un progetto core .net in cui eseguo test di integrazione con un database sqlite. I test sono stati ancora eseguiti in parallelo. La risposta accettata ha funzionato però.
user1796440

Grazie mille per questa risposta! Necessario per farlo perché ho test di accettazione in classi diverse che ereditano entrambi dallo stesso TestBase e la concorrenza non stava funzionando bene con EF Core.
Kyanite

104

Come affermato sopra, tutti i buoni unit test dovrebbero essere isolati al 100%. L'utilizzo dello stato condiviso (ad esempio, a seconda di una staticproprietà che viene modificata da ciascun test) è considerato una cattiva pratica.

Detto questo, la tua domanda sull'esecuzione dei test xUnit in sequenza ha una risposta! Ho riscontrato esattamente lo stesso problema perché il mio sistema utilizza un localizzatore di servizi statici (che è tutt'altro che ideale).

Per impostazione predefinita, xUnit 2.x esegue tutti i test in parallelo. Questo può essere modificato per assembly definendo il file CollectionBehaviorin AssemblyInfo.cs nel progetto di test.

Per la separazione per assemblaggio utilizzare:

using Xunit;
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]

o per nessuna parallelizzazione utilizzare:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Quest'ultimo è probabilmente quello che desideri. Ulteriori informazioni sulla parallelizzazione e la configurazione sono disponibili nella documentazione di xUnit .


5
Per me, c'erano risorse condivise tra i metodi all'interno di ogni classe. L'esecuzione di un test da una classe, poi uno da un'altra, interromperebbe i test di entrambe. Sono stato in grado di risolvere utilizzando [assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]. Grazie a te, @Squiggle, posso fare tutti i miei test e andare a prendere un caffè! :)
Alielson Piffer

2
La risposta di Abhinav Saxena è più granulare per .NET Core.
Yennefer

65

Per i progetti .NET Core, crea xunit.runner.jsoncon:

{
  "parallelizeAssembly": false,
  "parallelizeTestCollections": false
}

Inoltre, il tuo csprojdovrebbe contenere

<ItemGroup>
  <None Update="xunit.runner.json"> 
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

Per i vecchi progetti .Net Core, project.jsondovresti contenere

"buildOptions": {
  "copyToOutput": {
    "include": [ "xunit.runner.json" ]
  }
}

2
Presumo che l'ultimo equivalente del core dotnet di csproj sarebbe <ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>o simile?
Squiggle

3
Questo ha funzionato per me in csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd

La disabilitazione della parallelizzazione funziona con le teorie xUnit?
John Zabroski

Questa è l'unica cosa che ha funzionato per me, ho provato a correre dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falsema non ha funzionato per me.
Harvey

18

Per i progetti .NET Core, puoi configurare xUnit con un xunit.runner.jsonfile, come documentato su https://xunit.github.io/docs/configuring-with-json.html .

L'impostazione che è necessario modificare per interrompere l'esecuzione del test parallelo è parallelizeTestCollections, che per impostazione predefinita è true:

Impostalo su truese l'assembly è disposto a eseguire test all'interno di questo assembly in parallelo l'uno contro l'altro. ... Impostalo su falseper disabilitare tutta la parallelizzazione all'interno di questo assembly di test.

Tipo di schema JSON: booleano
Valore predefinito:true

Quindi xunit.runner.jsonsembra un minimo per questo scopo

{
    "parallelizeTestCollections": false
}

Come indicato nella documentazione, ricorda di includere questo file nella tua build, in uno dei seguenti modi:

  • Impostazione di Copia nella directory di output su Copia se più recente nelle Proprietà del file in Visual Studio o
  • Aggiunta

    <Content Include=".\xunit.runner.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>

    al tuo .csprojfile, o

  • Aggiunta

    "buildOptions": {
      "copyToOutput": {
        "include": [ "xunit.runner.json" ]
      }
    }

    al tuo project.jsonfile

a seconda del tipo di progetto.

Infine, oltre a quanto sopra, se stai usando Visual Studio, assicurati di non aver fatto clic accidentalmente sul pulsante Esegui test in parallelo , il che farà sì che i test vengano eseguiti in parallelo anche se hai disattivato la parallelizzazione in xunit.runner.json. I progettisti dell'interfaccia utente di Microsoft hanno abilmente reso questo pulsante senza etichetta, difficile da notare e distante circa un centimetro dal pulsante "Esegui tutto" in Esplora test, solo per massimizzare la possibilità che tu lo prema per errore e non hai idea del perché i tuoi test stanno improvvisamente fallendo:

Screenshot con il pulsante cerchiato


@JohnZabroski Non capisco la tua modifica suggerita . Cosa c'entra ReSharper con qualcosa? Penso di averlo installato quando ho scritto la risposta sopra, ma non è tutto qui indipendentemente dal fatto che tu lo stia usando o meno? Cosa ha a che fare la pagina a cui ti colleghi nella modifica con la specifica di un xunit.runner.jsonfile? E cosa xunit.runner.jsonha a che fare la specifica di un con l'esecuzione dei test in serie?
Mark Amery

Sto cercando di eseguire i miei test in serie e inizialmente pensavo che il problema fosse correlato a ReSharper (poiché ReSharper NON ha il pulsante "Esegui test in parallelo" come fa Visual Studio Test Explorer). Tuttavia, sembra che quando uso [Theory], i miei test non siano isolati. Questo è strano, perché tutto ciò che leggo suggerisce che una Classe è la più piccola unità parallelizzabile.
John Zabroski

8

Questa è una vecchia domanda ma volevo scrivere una soluzione per le persone che cercano di recente come me :)

Nota: utilizzo questo metodo nei test di integrazione Dot Net Core WebUI con xunit versione 2.4.1.

Creare una classe vuota denominata NonParallelCollectionDefinitionClass e quindi assegnare l'attributo CollectionDefinition a questa classe come di seguito. (La parte importante è DisableParallelization = true setting.)

using Xunit;

namespace WebUI.IntegrationTests.Common
{
    [CollectionDefinition("Non-Parallel Collection", DisableParallelization = true)]
    public class NonParallelCollectionDefinitionClass
    {
    }
}

Quindi aggiungi l'attributo Collection alla classe che non vuoi che venga eseguita in parallelo come di seguito. (La parte importante è il nome della raccolta. Deve essere lo stesso del nome utilizzato in CollectionDefinition)

namespace WebUI.IntegrationTests.Controllers.Users
{
    [Collection("Non-Parallel Collection")]
    public class ChangePassword : IClassFixture<CustomWebApplicationFactory<Startup>>
    ...

Quando lo facciamo, in primo luogo vengono eseguiti altri test paralleli. Successivamente vengono eseguiti gli altri test che hanno l'attributo Collection ("Non-Parallel Collection").


6

puoi usare la playlist

fare clic con il tasto destro sul metodo di prova -> Aggiungi a playlist -> Nuova playlist

quindi puoi specificare l'ordine di esecuzione, l'impostazione predefinita è, quando li aggiungi alla playlist ma puoi cambiare il file della playlist come vuoi

inserisci qui la descrizione dell'immagine


5

Non conosco i dettagli, ma sembra che tu stia cercando di fare test di integrazione piuttosto che test di unità . Se potessi isolare la dipendenza da ServiceHost, questo probabilmente renderebbe il tuo test più facile (e veloce). Quindi (ad esempio) potresti testare quanto segue in modo indipendente:

  • Classe di lettura della configurazione
  • ServiceHost factory (possibilmente come test di integrazione)
  • Classe del motore che richiede un IServiceHostFactorye unIConfiguration

Strumenti che potrebbero aiutare a includere framework di isolamento (mocking) e (facoltativamente) framework di contenitori IoC. Vedere:


Non sto cercando di eseguire test di integrazione. Ho davvero bisogno di fare unit test. Sono completamente esperto in termini e pratiche TDD / BDD (IoC, DI, Mocking, ecc.), Quindi il tipo di cose ordinarie come la creazione di fabbriche e l'uso di interfacce non è ciò di cui ho bisogno (è già fatto, tranne nel caso di ServiceHost stesso.) ServiceHost non è una dipendenza che può essere isolata, in quanto non è adeguatamente mockable (come gran parte degli spazi dei nomi del sistema .NET). Ho davvero bisogno di un modo per eseguire gli unit test in serie.
jrista,

1
@jrista - non era previsto alcun lieve sulle tue abilità. Non sono uno sviluppatore WCF, ma sarebbe possibile per il motore restituire un wrapper attorno a ServiceHost con un'interfaccia sul wrapper? O forse una fabbrica personalizzata per ServiceHosts?
TrueWill,

Il motore di hosting non restituisce alcun ServiceHost. In realtà non restituisce nulla, gestisce semplicemente la creazione, l'apertura e la chiusura di ServiceHosts internamente. Potrei racchiudere tutti i tipi fondamentali di WCF, ma è MOLTO lavoro che non sono stato autorizzato a fare. Inoltre, come si è scoperto, il problema non è causato dall'esecuzione parallela e si verificherà comunque durante il normale funzionamento. Ho iniziato un'altra domanda qui su SO sul problema e spero di ottenere una risposta.
jrista

@TrueWill: BTW, non ero preoccupato che tu ignorassi le mie capacità ... semplicemente non volevo ottenere molte risposte banali che coprissero tutte le cose comuni sui test di unità. Avevo bisogno di una risposta rapida su un problema molto specifico. Scusa se sono uscito un po 'burbero, non era mia intenzione. Ho solo un tempo piuttosto limitato per far funzionare questa cosa.
jrista

3

Forse puoi usare Advanced Unit Testing . Ti consente di definire la sequenza in cui eseguire il test . Quindi potrebbe essere necessario creare un nuovo file cs per ospitare quei test.

Ecco come puoi piegare i metodi di prova per lavorare nella sequenza che desideri.

[Test]
[Sequence(16)]
[Requires("POConstructor")]
[Requires("WorkOrderConstructor")]
public void ClosePO()
{
  po.Close();

  // one charge slip should be added to both work orders

  Assertion.Assert(wo1.ChargeSlipCount==1,
    "First work order: ChargeSlipCount not 1.");
  Assertion.Assert(wo2.ChargeSlipCount==1,
    "Second work order: ChargeSlipCount not 1.");
  ...
}

Fammi sapere se funziona.


Ottimo articolo. In realtà l'ho inserito nei segnalibri su CP. Grazie per il collegamento, ma come si è scoperto, il problema sembra essere molto più profondo, poiché i test runner non sembrano eseguire i test in parallelo.
jrista,

2
Aspetta, prima dici che non vuoi che il test venga eseguito in parallelo, e poi dici che il problema è che i test runner non eseguono i test in parallelo ... quindi qual è quale?
Graviton,

Il collegamento fornito non funziona più. Ed è qualcosa che puoi fare con xunit?
Allen Wang


0

Ho aggiunto l'attributo [Collection ("Sequential")] in una classe base:

    namespace IntegrationTests
    {
      [Collection("Sequential")]
      public class SequentialTest : IDisposable
      ...


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }

0

Nessuna delle risposte suggerite finora ha funzionato per me. Ho un'app dotnet core con XUnit 2.4.1. Ho ottenuto il comportamento desiderato con una soluzione alternativa inserendo un blocco in ogni unit test. Nel mio caso, non mi importava dell'ordine di esecuzione, solo che i test erano sequenziali.

public class TestClass
{
    [Fact]
    void Test1()
    {
        lock (this)
        {
            //Test Code
        }
    }

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
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.