Wykonywanie testów jednostkowych szeregowo (zamiast równolegle)


106

Próbuję przetestować jednostkę do aparatu zarządzania hostami WCF, który napisałem. Silnik zasadniczo tworzy wystąpienia ServiceHost w locie na podstawie konfiguracji. Dzięki temu możemy dynamicznie zmieniać konfigurację dostępnych usług bez konieczności wyłączania ich wszystkich i ponownego uruchamiania za każdym razem, gdy dodawana jest nowa usługa lub usuwana jest stara.

Mam jednak trudności z testowaniem jednostkowym tego silnika zarządzania hostem ze względu na sposób działania ServiceHost. Jeśli ServiceHost został już utworzony, otwarty i jeszcze nie zamknięty dla określonego punktu końcowego, nie można utworzyć innego ServiceHost dla tego samego punktu końcowego, co powoduje wyjątek. Ze względu na fakt, że nowoczesne platformy testów jednostkowych równolegle wykonują swoje testy, nie mam skutecznego sposobu na testowanie jednostkowe tego fragmentu kodu.

Korzystałem z xUnit.NET, mając nadzieję, że ze względu na jego rozszerzalność uda mi się znaleźć sposób, aby zmusić go do seryjnego uruchamiania testów. Jednak nie miałem szczęścia. Mam nadzieję, że ktoś tutaj na SO napotkał podobny problem i wie, jak sprawić, aby testy jednostkowe były uruchamiane seryjnie.

UWAGA: ServiceHost to klasa WCF napisana przez firmę Microsoft. Nie mam możliwości zmiany tego zachowania. Hostowanie każdego punktu końcowego usługi tylko raz jest również właściwym zachowaniem ... jednak nie jest szczególnie sprzyjające testom jednostkowym.


Czy to konkretne zachowanie ServiceHost nie byłoby czymś, czym możesz chcieć się zająć?
Robert Harvey,

ServiceHost jest napisany przez firmę Microsoft. Nie mam nad tym kontroli. Mówiąc technicznie, jest to prawidłowe zachowanie ... nigdy nie powinieneś mieć więcej niż jednego ServiceHost na punkt końcowy.
jrista

1
Miałem podobny problem, próbując uruchomić wiele TestServerw dockerze. Musiałem więc serializować testy integracyjne.
h-rai

Odpowiedzi:


128

Każda klasa testowa jest unikatową kolekcją testów, a testy w jej ramach będą uruchamiane sekwencyjnie, więc jeśli umieścisz wszystkie testy w tej samej kolekcji, będą one uruchamiane sekwencyjnie.

W xUnit możesz wprowadzić następujące zmiany, aby to osiągnąć:

Równolegle będą działać:

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

Aby uczynić go sekwencyjnym, wystarczy umieścić obie klasy testowe w tej samej kolekcji:

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

Więcej informacji można znaleźć pod tym linkiem


27
Myślę, że niedoceniona odpowiedź. Wygląda na to, że działa i podoba mi się ziarnistość, ponieważ mam równoległe i nierównoległe testy w jednym zestawie.
Igand

1
To jest właściwy sposób, aby to zrobić, zapoznaj się z dokumentacją Xunit.
Håkon K. Olafsen

2
To powinna być akceptowana odpowiedź, ponieważ zazwyczaj niektóre testy mogą być uruchamiane równolegle (w moim przypadku wszystkie testy jednostkowe), ale niektóre kończą się niepowodzeniem, gdy są uruchamiane równolegle (w moim przypadku te używające klienta / serwera WWW w pamięci) w stanie zoptymalizować przebieg testów, jeśli ktoś sobie tego życzy.
Aleksiej,

2
To nie zadziałało w przypadku podstawowego projektu .net, w którym wykonuję testy integracji z bazą danych sqlite. Testy były nadal wykonywane równolegle. Zaakceptowana odpowiedź jednak zadziałała.
user1796440

Dziękuję bardzo za tę odpowiedź! Potrzebne do zrobienia tego, ponieważ mam testy akceptacji w różnych klasach, które dziedziczą z tej samej TestBase, a współbieżność nie grała dobrze z EF Core.
Kyanite,

108

Ważne: ta odpowiedź dotyczy .NET Framework. Aby uzyskać dotnet core, zobacz odpowiedź Dimitry'ego dotyczącą xunit.runner.json.

Wszystkie dobre testy jednostkowe powinny być izolowane w 100%. Używanie stanu wspólnego (np. W zależności od staticwłaściwości modyfikowanej przez każdy test) jest uważane za złą praktykę.

To powiedziawszy, Twoje pytanie dotyczące uruchamiania testów xUnit w kolejności ma odpowiedź! Napotkałem dokładnie ten sam problem, ponieważ mój system używa statycznego lokalizatora usług (który jest mniej niż idealny).

Domyślnie xUnit 2.x uruchamia wszystkie testy równolegle. Można to zmodyfikować dla każdego zestawu, definiując CollectionBehaviorw swoim AssemblyInfo.cs w projekcie testowym.

W przypadku separacji na zespół użyj:

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

lub w przypadku całkowitego braku zrównoleglania:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Ten ostatni jest prawdopodobnie tym, którego chcesz. Więcej informacji na temat zrównoleglania i konfiguracji można znaleźć w dokumentacji xUnit .


5
W moim przypadku zasoby były współdzielone między metodami w ramach każdej klasy. Uruchomienie testu z jednej klasy, a następnie z drugiej, spowodowałoby przerwanie testów z obu. Udało mi się rozwiązać za pomocą [assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]. Dzięki tobie @Squiggle mogę przeprowadzić wszystkie testy i pójść na kawę! :)
Alielson Piffer

2
Odpowiedź Abhinava Saxena jest bardziej szczegółowa w przypadku platformy .NET Core.
Yennefer

73

W przypadku projektów .NET Core utwórz za xunit.runner.jsonpomocą:

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

Należy również pamiętać, csprojpowinna zawierać

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

W przypadku starych projektów .Net Core project.jsonpowinieneś zawierać

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

2
Zakładam, że najnowszy odpowiednik rdzenia csproj dotnet byłby <ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>lub podobny?
Squiggle,

3
To zadziałało dla mnie w csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd

Czy wyłączenie równoległości działa z teoriami xUnit?
John Zabroski

To jedyna rzecz, która działała dla mnie, próbowałem biegać, dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falseale nie zadziałało.
harvzor

18

W przypadku projektów .NET Core można skonfigurować xUnit za pomocą xunit.runner.jsonpliku, zgodnie z dokumentacją pod adresem https://xunit.github.io/docs/configuring-with-json.html .

Ustawienie, które należy zmienić, aby zatrzymać równoległe wykonywanie testów, to parallelizeTestCollectionsdomyślnie true:

Ustaw tę opcję na, truejeśli zestaw chce uruchomić testy wewnątrz tego zespołu równolegle względem siebie. ... Ustaw tę falseopcję, aby wyłączyć całą równoległość w tym zestawie testowym.

Typ schematu JSON: boolean
Wartość domyślna:true

Tak xunit.runner.jsonwygląda minimum do tego celu

{
    "parallelizeTestCollections": false
}

Jak wspomniano w dokumentacji, pamiętaj, aby dołączyć ten plik do swojej kompilacji:

  • Ustawianie Kopiuj do Output Directory, aby Copy jeśli nowsze w pliku za Properties w Visual Studio, lub
  • Dodawanie

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

    do swojego .csprojpliku lub

  • Dodawanie

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

    do twojego project.jsonpliku

w zależności od rodzaju projektu.

Na koniec, oprócz powyższego, jeśli używasz programu Visual Studio, upewnij się, że przypadkowo nie kliknąłeś przycisku Uruchom testy równolegle , co spowoduje równoległe uruchamianie testów, nawet jeśli wyłączyłeś równoległość w xunit.runner.json. Projektanci interfejsu użytkownika Microsoftu sprytnie sprawili, że ten przycisk nie został oznaczony, był trudny do zauważenia i około centymetra od przycisku „Uruchom wszystko” w Eksploratorze testów, aby zmaksymalizować szansę, że trafisz go przez pomyłkę i nie masz pojęcia, dlaczego testy nagle zawodzą:

Zrzut ekranu z przyciskiem w kółku


@JohnZabroski Nie rozumiem twojej sugerowanej zmiany . Co ReSharper ma wspólnego z czymkolwiek? Myślę, że prawdopodobnie zainstalowałem go, kiedy napisałem powyższą odpowiedź, ale czy nie wszystko jest tutaj niezależne od tego, czy go używasz, czy nie? Co strona, do której podajesz link w edycji, ma wspólnego z określeniem xunit.runner.jsonpliku? A co ma xunit.runner.jsonwspólnego określenie , czy testy są uruchamiane seryjnie?
Mark Amery

Próbuję uruchomić moje testy seryjnie i początkowo myślałem, że problem jest związany z ReSharper (ponieważ ReSharper NIE ma przycisku „Uruchom testy równolegle”, tak jak robi to Visual Studio Test Explorer). Jednak wydaje mi się, że kiedy używam [Teorii], moje testy nie są izolowane. Jest to dziwne, ponieważ wszystko, co przeczytałem, sugeruje, że klasa jest najmniejszą jednostką z możliwością równoległości.
John Zabroski

10

To stare pytanie, ale chciałem napisać rozwiązanie dla osób szukających nowych, takich jak ja :)

Uwaga: używam tej metody w testach integracji Dot Net Core WebUI z xunit w wersji 2.4.1.

Utwórz pustą klasę o nazwie NonParallelCollectionDefinitionClass, a następnie nadaj tej klasie atrybut CollectionDefinition, jak poniżej. (Ważną częścią jest DisableParallelization = ustawienie true).

using Xunit;

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

Następnie dodaj atrybut Collection do klasy, której nie chcesz, aby działała równolegle, jak poniżej. (Ważną częścią jest nazwa kolekcji. Musi być taka sama jak nazwa użyta w CollectionDefinition)

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

Kiedy to robimy, najpierw uruchamiane są inne testy równoległe. Następnie uruchamiane są inne testy, które mają atrybut Collection („Non-Parallel Collection”).


6

możesz użyć listy odtwarzania

kliknij prawym przyciskiem myszy metodę testową -> Dodaj do listy odtwarzania -> Nowa lista odtwarzania

wtedy możesz określić kolejność wykonywania, domyślnie jest tak, jak dodajesz je do listy odtwarzania, ale możesz zmienić plik listy odtwarzania, jak chcesz

wprowadź opis obrazu tutaj


5

Nie znam szczegółów, ale wygląda na to, że próbujesz przeprowadzić testy integracyjne, a nie jednostkowe . Gdybyś mógł wyodrębnić zależność od ServiceHost, prawdopodobnie ułatwiłoby to (i przyspieszy) testowanie. Na przykład możesz niezależnie przetestować następujące elementy:

  • Klasa czytania konfiguracji
  • ServiceHost factory (prawdopodobnie jako test integracji)
  • Klasa silnika, która przyjmuje an IServiceHostFactoryiIConfiguration

Narzędzia, które pomogłyby obejmować struktury izolacji (symulowanie) i (opcjonalnie) struktury kontenerów IoC. Widzieć:


Nie próbuję przeprowadzać testów integracji. Rzeczywiście muszę przeprowadzić testy jednostkowe. Jestem dogłębnie zaznajomiony z terminami i praktykami TDD / BDD (IoC, DI, Mocking itp.), Więc zwykłe rzeczy, takie jak tworzenie fabryk i używanie interfejsów, nie są tym, czego potrzebuję (jest już zrobione, z wyjątkiem przypadku samego ServiceHost.) ServiceHost nie jest zależnością, którą można izolować, ponieważ nie jest poprawnie makowalna (podobnie jak wiele przestrzeni nazw systemu .NET). Naprawdę potrzebuję sposobu na szeregowe uruchamianie testów jednostkowych.
jrista

1
@jrista - nie zamierzano lekceważyć twoich umiejętności. Nie jestem programistą WCF, ale czy aparat mógłby zwrócić opakowanie wokół ServiceHost z interfejsem w opakowaniu? A może niestandardowa fabryka dla ServiceHosts?
TrueWill

Silnik hostingowy nie zwraca żadnych ServiceHosts. W rzeczywistości nic nie zwraca, po prostu wewnętrznie zarządza tworzeniem, otwieraniem i zamykaniem ServiceHosts. Mógłbym opakować wszystkie podstawowe typy WCF, ale jest to DUŻO pracy, do której tak naprawdę nie mam uprawnień. Ponadto, jak się okazało, problem nie jest spowodowany równoległym wykonaniem i nadal będzie występował podczas normalnej pracy. Zacząłem tutaj kolejne pytanie na SO dotyczące problemu i mam nadzieję, że otrzymam odpowiedź.
jrista,

@TrueWill: Przy okazji, nie martwiłem się, że w ogóle lekceważysz moje umiejętności ... Po prostu nie chciałem otrzymywać wielu zwykłych odpowiedzi, które obejmują wszystkie typowe rzeczy dotyczące testów jednostkowych. Potrzebowałem szybkiej odpowiedzi na bardzo konkretny problem. Przepraszam, jeśli wypadłem trochę szorstko, to nie był mój zamiar. Po prostu mam dość ograniczony czas, żeby to zadziałało.
jrista,

3

Może możesz skorzystać z zaawansowanych testów jednostkowych . Pozwala zdefiniować kolejność, w jakiej przeprowadzasz test . Być może będziesz musiał utworzyć nowy plik cs do obsługi tych testów.

Oto, jak możesz nagiąć metody testowe, aby działały w żądanej kolejności.

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

Daj mi znać, czy to działa.


Świetny artykuł. Właściwie to miałem zakładkę na CP. Dzięki za link, ale jak się okazało, problem wydaje się być znacznie głębszy, ponieważ testerzy nie wydają się uruchamiać testów równolegle.
jrista,

2
Poczekaj, najpierw mówisz, że nie chcesz, aby test był uruchamiany równolegle, a następnie mówisz, że problem polega na tym, że programy uruchamiające testy nie uruchamiają testów równolegle ... więc która jest która?
Graviton

Podany link już nie działa. I czy to jest coś, co możesz zrobić z Xunit?
Allen Wang,


0

Dodałem atrybut [Collection ("Sequential")] w klasie bazowej:

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


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }

0

Żadna z dotychczas sugerowanych odpowiedzi nie zadziałała dla mnie. Mam aplikację dotnet core z XUnit 2.4.1. Osiągnąłem pożądane zachowanie, stosując obejście, umieszczając blokadę w każdym teście jednostkowym. W moim przypadku nie przejmowałem się kolejnością uruchamiania, tylko to, że testy były sekwencyjne.

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

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.