Unit-Tests seriell (statt parallel) ausführen

96

Ich versuche, eine von mir geschriebene WCF-Host-Management-Engine zu testen. Die Engine erstellt ServiceHost-Instanzen grundsätzlich im laufenden Betrieb basierend auf der Konfiguration. Auf diese Weise können wir dynamisch neu konfigurieren, welche Dienste verfügbar sind, ohne alle herunterfahren und neu starten zu müssen, wenn ein neuer Dienst hinzugefügt oder ein alter entfernt wird.

Ich habe jedoch aufgrund der Funktionsweise von ServiceHost Schwierigkeiten beim Testen dieser Host-Management-Engine. Wenn für einen bestimmten Endpunkt bereits ein ServiceHost erstellt, geöffnet und noch nicht geschlossen wurde, kann kein anderer ServiceHost für denselben Endpunkt erstellt werden, was zu einer Ausnahme führt. Aufgrund der Tatsache, dass moderne Unit-Test-Plattformen ihre Testausführung parallelisieren, habe ich keine effektive Möglichkeit, diesen Code zu testen.

Ich habe xUnit.NET verwendet, in der Hoffnung, dass ich aufgrund seiner Erweiterbarkeit einen Weg finden könnte, es zu zwingen, die Tests seriell auszuführen. Ich hatte jedoch kein Glück. Ich hoffe, dass jemand hier auf SO auf ein ähnliches Problem gestoßen ist und weiß, wie Unit-Tests seriell ausgeführt werden können.

HINWEIS: ServiceHost ist eine WCF-Klasse, die von Microsoft geschrieben wurde. Ich habe nicht die Fähigkeit, sein Verhalten zu ändern. Das einmalige Hosten jedes Service-Endpunkts ist ebenfalls das richtige Verhalten. Es ist jedoch für Unit-Tests nicht besonders förderlich.

jrista
quelle
Wäre dieses spezielle Verhalten von ServiceHost nicht etwas, das Sie möglicherweise ansprechen möchten?
Robert Harvey
ServiceHost wurde von Microsoft geschrieben. Ich habe keine Kontrolle darüber. Und technisch gesehen ist es ein gültiges Verhalten ... Sie sollten niemals mehr als einen ServiceHost pro Endpunkt haben.
jrista
1
Ich hatte ein ähnliches Problem beim Versuch, mehrere TestServerim Docker auszuführen . Also musste ich die Integrationstests serialisieren.
h-rai

Antworten:

113

Jede Testklasse ist eine eindeutige Testsammlung, und die Tests darunter werden nacheinander ausgeführt. Wenn Sie also alle Ihre Tests in derselben Sammlung ablegen, wird sie nacheinander ausgeführt.

In xUnit können Sie folgende Änderungen vornehmen, um dies zu erreichen:

Folgendes läuft parallel:

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

Um es sequentiell zu machen, müssen Sie nur beide Testklassen unter dieselbe Sammlung stellen:

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

Weitere Informationen finden Sie unter diesem Link

Abhinav Saxena
quelle
23
Unterschätzte Antwort, denke ich. Scheint zu funktionieren und ich mag die Granularität, da ich parallelisierbare und nicht parallelisierbare Tests in einer Baugruppe habe.
Igand
1
Dies ist der richtige Weg, siehe Xunit-Dokumentation.
Håkon K. Olafsen
2
Dies sollte die akzeptierte Antwort sein, da normalerweise einige Tests parallel ausgeführt werden können (in meinem Fall alle Komponententests), andere jedoch zufällig fehlschlagen, wenn sie parallel ausgeführt werden (in meinem Fall diejenigen, die einen speicherinternen Webclient / -server verwenden) in der Lage, den Testlauf zu optimieren, wenn man dies wünscht.
Alexei
2
Dies funktionierte bei mir in einem .net-Kernprojekt nicht, in dem ich Integrationstests mit einer SQLite-Datenbank durchführe. Die Tests wurden noch parallel durchgeführt. Die akzeptierte Antwort hat jedoch funktioniert.
user1796440
Vielen Dank für diese Antwort! Ich musste dies tun, da ich Akzeptanztests in verschiedenen Klassen habe, die beide von derselben TestBase erben und die Parallelität mit EF Core nicht gut funktioniert.
Kyanite
104

Wie oben angegeben, sollten alle guten Unit-Tests zu 100% isoliert sein. Die Verwendung eines gemeinsam genutzten Status (z. B. abhängig von einer staticEigenschaft, die von jedem Test geändert wird) wird als schlechte Praxis angesehen.

Allerdings hat Ihre Frage zum Ausführen von xUnit-Tests nacheinander eine Antwort! Ich habe genau das gleiche Problem festgestellt, weil mein System einen statischen Service-Locator verwendet (der nicht ideal ist).

Standardmäßig führt xUnit 2.x alle Tests parallel aus. Dies kann pro Baugruppe geändert werden, indem Sie die CollectionBehaviorin Ihrer AssemblyInfo.cs in Ihrem Testprojekt definieren.

Für die Trennung pro Baugruppe verwenden:

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

oder für überhaupt keine Parallelisierung verwenden:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Letzteres ist wahrscheinlich das, was Sie wollen. Weitere Informationen zur Parallelisierung und Konfiguration finden Sie in der xUnit-Dokumentation .

Kringel
quelle
5
Für mich gab es gemeinsame Ressourcen zwischen Methoden innerhalb jeder Klasse. Wenn Sie einen Test von einer Klasse und dann von einer anderen ausführen, werden die Tests von beiden Klassen unterbrochen. Ich konnte mit lösen [assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]. Dank dir, @Squiggle, kann ich alle meine Tests durchführen und einen Kaffee trinken gehen! :)
Alielson Piffer
2
Die Antwort von Abhinav Saxena ist für .NET Core detaillierter.
Yennefer
64

Erstellen Sie für .NET Core-Projekte Folgendes xunit.runner.json:

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

Auch sollte Ihr csprojenthalten

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

Für alte .Net Core-Projekte sollte Ihr project.jsonenthalten

"buildOptions": {
  "copyToOutput": {
    "include": [ "xunit.runner.json" ]
  }
}
Dmitry Polomoshnov
quelle
2
Ich gehe davon aus, dass das neueste csproj dotnet-Kernäquivalent <ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>oder ähnlich wäre.
Squiggle
3
Dies funktionierte für mich in csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd
Funktioniert das Deaktivieren der Parallelisierung mit xUnit Theories?
John Zabroski
Dies ist das einzige, was für mich funktioniert hat. Ich habe versucht, wie zu laufen, dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falseaber es hat bei mir nicht funktioniert.
Harvey
18

Für .NET Core-Projekte können Sie xUnit mit einer xunit.runner.jsonDatei konfigurieren , wie unter https://xunit.github.io/docs/configuring-with-json.html dokumentiert .

Die Einstellung, die Sie ändern müssen, um die parallele Testausführung zu stoppen, lautet parallelizeTestCollectionsstandardmäßig true:

Stellen Sie dies ein, truewenn die Assembly bereit ist, Tests innerhalb dieser Assembly parallel gegeneinander auszuführen. ... Setzen Sie dies auf false, um die gesamte Parallelisierung innerhalb dieser Testbaugruppe zu deaktivieren.

JSON-Schematyp: boolean
Standardwert:true

So xunit.runner.jsonsieht ein Minimum für diesen Zweck aus

{
    "parallelizeTestCollections": false
}

Denken Sie daran, diese Datei, wie in den Dokumenten angegeben, in Ihren Build aufzunehmen, entweder durch:

  • Festlegen von " Kopieren in Ausgabeverzeichnis" auf " Kopieren", falls in den Eigenschaften der Datei in Visual Studio oder neu
  • Hinzufügen

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

    zu Ihrer .csprojDatei, oder

  • Hinzufügen

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

    zu Ihrer project.jsonDatei

abhängig von Ihrem Projekttyp.

Schließlich zusätzlich zu den oben genannten, wenn Sie Visual Studio dann verwenden stellen Sie sicher , dass Sie nicht versehentlich die geklickt haben , Run Tests Parallel - Taste, die Tests werden dazu führen , parallel laufen zu lassen , auch wenn Sie aus Parallelisierung in aktiviert haben xunit.runner.json. Die UI-Designer von Microsoft haben diese Schaltfläche schlau unbeschriftet, schwer zu bemerken und etwa einen Zentimeter von der Schaltfläche "Alle ausführen " im Test-Explorer entfernt, um die Wahrscheinlichkeit zu maximieren, dass Sie sie versehentlich treffen und keine Ahnung haben, warum Ihre Tests durchgeführt wurden scheitern plötzlich:

Screenshot mit eingekreistem Button

Mark Amery
quelle
@ JohnZabroski Ich verstehe deine vorgeschlagene Bearbeitung nicht . Was hat ReSharper mit irgendetwas zu tun? Ich glaube, ich hatte es wahrscheinlich installiert, als ich die Antwort oben schrieb, aber ist hier nicht alles unabhängig davon, ob Sie es verwenden oder nicht? Was hat die Seite, auf die Sie in der Bearbeitung verlinken, mit der Angabe einer xunit.runner.jsonDatei zu tun ? Und was hat die Angabe von xunit.runner.jsondamit zu tun, dass Tests seriell ausgeführt werden?
Mark Amery
Ich versuche, meine Tests seriell auszuführen, und dachte zunächst, dass das Problem mit ReSharper zusammenhängt (da ReSharper NICHT über die Schaltfläche "Tests parallel ausführen" verfügt, wie dies im Visual Studio Test Explorer der Fall ist). Es scheint jedoch, dass meine Tests nicht isoliert sind, wenn ich [Theorie] verwende. Das ist seltsam, weil alles, was ich lese, darauf hindeutet, dass eine Klasse die kleinste parallelisierbare Einheit ist.
John Zabroski
8

Dies ist eine alte Frage, aber ich wollte eine Lösung für Leute schreiben, die wie ich neu suchen :)

Hinweis: Ich verwende diese Methode in Dot Net Core WebUI-Integrationstests mit xunit Version 2.4.1.

Erstellen Sie eine leere Klasse mit dem Namen NonParallelCollectionDefinitionClass und geben Sie dieser Klasse das CollectionDefinition-Attribut wie folgt. (Der wichtige Teil ist DisableParallelization = true-Einstellung.)

using Xunit;

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

Fügen Sie anschließend der Klasse das Collection-Attribut hinzu, das nicht wie unten gezeigt parallel ausgeführt werden soll. (Der wichtige Teil ist der Name der Sammlung. Er muss mit dem in CollectionDefinition verwendeten Namen identisch sein.)

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

Wenn wir dies tun, werden zunächst andere parallele Tests ausgeführt. Danach werden die anderen Tests mit dem Attribut "Sammlung" ("Nicht parallele Sammlung") ausgeführt.

Gündüz Emre ÖZER
quelle
6

Sie können die Wiedergabeliste verwenden

Klicken Sie mit der rechten Maustaste auf die Testmethode -> Zur Wiedergabeliste hinzufügen -> Neue Wiedergabeliste

Anschließend können Sie die Ausführungsreihenfolge angeben. Die Standardeinstellung ist, wenn Sie sie zur Wiedergabeliste hinzufügen. Sie können die Wiedergabelistendatei jedoch beliebig ändern

Geben Sie hier die Bildbeschreibung ein

HB MAAM
quelle
5

Ich kenne die Details nicht, aber es scheint, als würden Sie eher versuchen, Integrationstests als Unit-Tests durchzuführen . Wenn Sie die Abhängigkeit von isolieren könnten ServiceHost, würde dies Ihre Tests wahrscheinlich einfacher (und schneller) machen. So können Sie (zum Beispiel) Folgendes unabhängig voneinander testen:

  • Konfigurationsleseklasse
  • ServiceHost-Fabrik (möglicherweise als Integrationstest)
  • Motorklasse, die ein IServiceHostFactoryund ein nimmtIConfiguration

Zu den Tools, die helfen würden, gehören Isolations- (Verspottungs-) Frameworks und (optional) IoC-Container-Frameworks. Sehen:

TrueWill
quelle
Ich versuche nicht, Integrationstests durchzuführen. Ich muss in der Tat Unit-Tests durchführen. Ich bin mit TDD / BDD-Begriffen und -Praktiken (IoC, DI, Mocking usw.) bestens vertraut. Daher ist es nicht das, was ich brauche (es ist bereits erledigt), wie man Fabriken erstellt und Schnittstellen verwendet. außer im Fall von ServiceHost selbst.) ServiceHost ist keine Abhängigkeit, die isoliert werden kann, da sie nicht richtig verspottbar ist (wie viele der .NET-System-Namespaces). Ich brauche wirklich eine Möglichkeit, die Komponententests seriell auszuführen.
Jrista
1
@jrista - nicht geringfügig auf Ihre Fähigkeiten war beabsichtigt. Ich bin kein WCF-Entwickler, aber könnte die Engine einen Wrapper um ServiceHost mit einer Schnittstelle auf dem Wrapper zurückgeben? Oder vielleicht eine kundenspezifische Fabrik für ServiceHosts?
TrueWill
Die Hosting-Engine gibt keine ServiceHosts zurück. Es gibt eigentlich nichts zurück, es verwaltet einfach das interne Erstellen, Öffnen und Schließen von ServiceHosts. Ich könnte alle grundlegenden WCF-Typen einpacken, aber das ist eine Menge Arbeit, zu der ich nicht wirklich befugt war. Wie sich herausstellte, wird das Problem nicht durch parallele Ausführung verursacht und tritt auch während des normalen Betriebs auf. Ich habe hier auf SO eine weitere Frage zum Problem gestellt und hoffe, dass ich eine Antwort bekomme.
jrista
@TrueWill: Übrigens, ich habe mir keine Sorgen gemacht, dass Sie meine Fähigkeiten beeinträchtigen könnten ... Ich wollte einfach nicht viele gewöhnliche Antworten erhalten, die alle gängigen Dinge über Unit-Tests abdecken. Ich brauchte eine schnelle Antwort auf ein ganz bestimmtes Problem. Tut mir leid, wenn ich ein bisschen schroff rausgekommen bin, war nicht meine Absicht. Ich habe nur ziemlich wenig Zeit, um dieses Ding zum Laufen zu bringen.
jrista
3

Möglicherweise können Sie Advanced Unit Testing verwenden . Hier können Sie die Reihenfolge definieren, in der Sie den Test ausführen . Daher müssen Sie möglicherweise eine neue CS-Datei erstellen, um diese Tests zu hosten.

Hier erfahren Sie, wie Sie die Testmethoden so biegen können, dass sie in der gewünschten Reihenfolge funktionieren.

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

Lassen Sie mich wissen, ob es funktioniert.

Graviton
quelle
Großartiger Artikel. Ich hatte es tatsächlich auf CP vorgemerkt. Vielen Dank für den Link, aber wie sich herausstellte, scheint das Problem viel tiefer zu liegen, da Testläufer keine parallelen Tests durchführen.
Jrista
2
Warten Sie, zuerst sagen Sie, Sie möchten nicht, dass der Test parallel ausgeführt wird, und dann sagen Sie, dass das Problem darin besteht, dass die Testläufer keine Tests parallel ausführen ... also welcher ist welcher?
Graviton
Der von Ihnen angegebene Link funktioniert nicht mehr. Und können Sie das mit xunit machen?
Allen Wang
0

Ich habe das Attribut [Collection ("Sequential")] in einer Basisklasse hinzugefügt :

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


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }
Matías Romero
quelle
0

Keine der vorgeschlagenen Antworten hat bisher für mich funktioniert. Ich habe eine Dotnet Core App mit XUnit 2.4.1. Ich habe das gewünschte Verhalten mit einer Problemumgehung erreicht, indem ich stattdessen in jedem Komponententest eine Sperre gesetzt habe. In meinem Fall war mir die laufende Reihenfolge egal, nur dass die Tests sequentiell waren.

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

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
Matt Hayes
quelle