Виконувати модульні тести послідовно (а не паралельно)


99

Я намагаюся модульно протестувати написаний мною механізм управління хостом WCF. В основному движок створює екземпляри ServiceHost на льоту на основі конфігурації. Це дозволяє нам динамічно переналаштовувати, які служби доступні, без необхідності збивати їх усі та перезапускати, коли додається нова послуга або видаляється стара.

Я зіткнувся з труднощами при модульному тестуванні цього механізму управління хостом, однак, через те, як працює ServiceHost. Якщо ServiceHost уже створений, відкритий і ще не закритий для певної кінцевої точки, не можна створити інший ServiceHost для тієї самої кінцевої точки, що спричиняє виняток. Через те, що сучасні платформи модульного тестування паралельно виконують тестування, я не маю ефективного способу модульного тестування цього фрагмента коду.

Я використовував xUnit.NET, сподіваючись, що через його розширюваність я зможу знайти спосіб змусити його запускати тести послідовно. Однак мені не пощастило. Я сподіваюся, що хтось тут, на SO, зіткнувся з подібною проблемою і знає, як отримати модульні тести для запуску послідовно.

ПРИМІТКА. ServiceHost - це клас WCF, написаний Microsoft. У мене немає можливості змінити свою поведінку. Хостинг кожної кінцевої точки служби лише один раз - це також правильна поведінка ... однак, це не особливо сприяє модульному тестуванню.


Чи не саме така поведінка ServiceHost може бути справою, з якою ви, можливо, хочете звернутися?
Роберт Харві,

ServiceHost написана корпорацією Майкрософт. Я не контролюю це. І технічно кажучи, це допустима поведінка ... ви ніколи не повинні мати більше одного ServiceHost на кінцеву точку.
jrista

1
У мене була подібна проблема при спробі запустити декілька TestServerв docker. Тож мені довелося серіалізувати тести інтеграції.
h-rai

Відповіді:


117

Кожен тестовий клас - це унікальна колекція тестів, і тести під ним будуть виконуватися послідовно, тому, якщо ви помістите всі свої тести в одну колекцію, вона буде виконуватися послідовно.

Для досягнення цього в xUnit можна внести такі зміни:

Паралельно буде виконуватися:

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

Щоб зробити його послідовним, вам просто потрібно помістити обидва тестові класи в одну колекцію:

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

Для отримання додаткової інформації ви можете звернутися за цим посиланням


24
Думаю, недооцінена відповідь. Здається, це працює, і мені подобається деталізація, оскільки у мене є паралелізаційні та непаралелізувані тести в одній збірці.
Іганд

1
Це правильний спосіб зробити це, перегляньте документацію Xunit.
Хокон К. Олафсен,

2
Це має бути прийнятою відповіддю, оскільки, як правило, деякі тести можна запускати паралельно (у моєму випадку всі модульні тести), але деякі випадають випадково при паралельному запуску (у моєму випадку ті, що використовують веб-клієнт / сервер в пам'яті), тому один здатний оптимізувати тестовий запуск, якщо хтось цього хоче.
Олексій

2
Це не спрацювало для мене в основному проекті .net, де я виконую тести інтеграції з базою даних sqlite. Тести все ще виконувались паралельно. Прийнята відповідь все ж спрацювала.
user1796440

Щиро дякую за цю відповідь! Потрібно зробити це, оскільки я маю прийомні тести в різних класах, які обидва успадковуються від однієї і тієї ж тестової бази, і паралельність не дуже гарна з EF Core.
кианит

104

Як зазначено вище, усі хороші одиничні тести повинні бути на 100% ізольованими. Використання спільного стану (наприклад, залежно від staticвластивості, яка модифікується кожним тестом) розглядається як погана практика.

Сказавши це, ваше запитання про тестування xUnit послідовно має відповідь! Я зіткнувся з точно такою ж проблемою, оскільки моя система використовує статичний локатор служб (що менше ідеального).

За замовчуванням xUnit 2.x запускає всі тести паралельно. Це можна змінити для кожної збірки, визначивши CollectionBehaviorу своєму AssemblyInfo.cs у вашому тестовому проекті.

Для розділення в зборі:

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

або взагалі не використовуйте розпаралелювання:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Остання, мабуть, та, яку ви хочете. Більше інформації про розпаралелювання та конфігурацію можна знайти в документації xUnit .


5
Для мене існували спільні ресурси між методами в кожному класі. Запуск тесту з одного класу, потім одного з іншого, порушить тести з обох. Я зміг вирішити проблему за допомогою [assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]. Завдяки тобі, @Squiggle, я можу пройти всі свої тести і піти каву! :)
Alielson Piffer

2
Відповідь Абхінава Саксени є більш детальною для .NET Core.
Йеннефер

67

Для проектів .NET Core створіть за xunit.runner.jsonдопомогою:

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

Крім того, ви csprojповинні містити

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

Для старих проектів .Net Core ви project.jsonповинні містити

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

2
Я припускаю, що останній еквівалент ядра csproj dotnet буде <ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>чи подібний?
Squiggle

3
Це спрацювало для мене в csproj:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd

Чи працює відключення розпаралелювання з теоріями xUnit?
Джон Заброські

Це єдине, що працювало у мене, я намагався бігати як, dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falseале у мене це не працювало.
Харві,

18

Для проектів .NET Core ви можете налаштувати xUnit із xunit.runner.jsonфайлом, як це вказано на https://xunit.github.io/docs/configuring-with-json.html .

Налаштування, яке потрібно змінити, щоб зупинити паралельне виконання тесту, має parallelizeTestCollectionsзначення за замовчуванням true:

Встановіть для цього значення, trueякщо збірка бажає запускати тести всередині цієї збірки паралельно один одному. ... Встановіть це значення, falseщоб вимкнути розпаралелювання в цій тестовій збірці.

Тип схеми JSON: логічне
значення Значення за замовчуванням:true

Тож мінімум xunit.runner.jsonдля цієї мети виглядає

{
    "parallelizeTestCollections": false
}

Як зазначено в документах, не забудьте включити цей файл у свою збірку, або:

  • Налаштування Копіювати у вихідний каталог для копіювання, якщо воно нове у властивостях файлу у Visual Studio, або
  • Додавання

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

    до вашого .csprojфайлу, або

  • Додавання

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

    до вашого project.jsonфайлу

залежно від типу вашого проекту.

Нарешті, на додаток до вищесказаного, якщо ви використовуєте Visual Studio, переконайтеся, що ви випадково не натиснули кнопку Виконати тести в паралелі , що призведе до паралельного запуску тестів, навіть якщо ви вимкнули паралелізацію в xunit.runner.json. Розробники інтерфейсу Microsoft хитро зробили цю кнопку без позначень, важко помітити, і приблизно на сантиметр від кнопки "Запустити все" в Провіднику тестів, щоб максимізувати шанс, що ви вдарите її помилково і не уявляєте, чому ваші тести раптом не вдаються:

Знімок екрана з обведеною кнопкою


@JohnZabroski Я не розумію запропонованого вами редагування . Яке відношення ReSharper до чогось має? Думаю, я, напевно, його встановив, коли писав відповідь вище, але чи тут все не залежить від того, використовуєте ви його чи ні? Яке відношення має сторінка, на яку ви посилаєтесь в редагуванні , із зазначенням xunit.runner.jsonфайлу? І що xunit.runner.jsonспільного із зазначенням значення має зробити, щоб тести запускалися послідовно?
Марк Амері

Я намагаюся, щоб мої тести запускалися послідовно, і спочатку вважав, що проблема пов'язана з ReSharper (оскільки ReSharper НЕ має кнопки "Запустити тести паралельно", як це робить Visual Studio Test Explorer). Однак, здається, коли я використовую [Теорію], мої тести не є ізольованими. Це дивно, бо все, що я читав, свідчить про те, що клас - це найменша паралелізується одиниця.
Джон Заброські,

9

Це старе питання, але я хотів написати рішення для людей, які шукають нещодавно, як я :)

Примітка: Я використовую цей метод у тестах інтеграції Dot Net Core WebUI з xunit версії 2.4.1.

Створіть порожній клас з ім'ям NonParallelCollectionDefinitionClass, а потім надайте атрибуту CollectionDefinition цьому класу, як показано нижче. (Важливою частиною є DisableParallelization = true.)

using Xunit;

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

Потім додайте атрибут Collection до класу, який ви не хочете, щоб він працював паралельно, як показано нижче. (Важливою частиною є назва колекції. Вона повинна збігатися з назвою, що використовується у CollectionDefinition)

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

Коли ми робимо це, спочатку запускаються інші паралельні тести. Після цього запускаються інші тести, які мають атрибут Collection ("Непаралельне збір").


6

Ви можете використовувати список відтворення

клацніть правою кнопкою миші на тестовому методі -> Додати до списку відтворення -> Новий список відтворення

тоді ви можете вказати порядок виконання, за замовчуванням це, коли ви додаєте їх до списку відтворення, але ви можете змінювати файл списку відтворення як завгодно

введіть тут опис зображення


5

Я не знаю деталей, але схоже, ви, можливо, намагаєтесь зробити інтеграційне тестування, а не модульне . Якби ви могли виділити залежність від ServiceHost, це, швидше за все, полегшило б (і швидше) тестування. Отже (наприклад) ви можете протестувати наступне самостійно:

  • Клас читання конфігурації
  • Завод ServiceHost (можливо, як тест інтеграції)
  • Клас двигуна, який приймає IServiceHostFactoryіIConfiguration

Інструменти, які могли б допомогти, включають ізоляційні (знущальні) фреймворки та (за бажанням) фреймворки контейнерів IoC. Побачити:


Я не намагаюся робити інтеграційне тестування. Мені справді потрібно провести модульне тестування. Я досконало розбираюсь у термінах та практиках TDD / BDD (IoC, DI, глузування тощо), тому пробіг таких млинних речей, як створення фабрик та використання інтерфейсів, - це не те, що мені потрібно (це вже зроблено, за винятком самого ServiceHost.) ServiceHost - це не залежність, яку можна ізолювати, оскільки вона належним чином не макетується (як більшість просторів імен .NET System.) Мені справді потрібен спосіб серійного запуску модульних тестів.
jrista

1
@jrista - жоден незначний твій вміння не передбачався. Я не розробник WCF, але чи можливо, щоб движок повернув обгортку навколо ServiceHost з інтерфейсом на обгортці? Або, можливо, спеціальна фабрика для ServiceHosts?
TrueWill

Механізм хостингу не повертає жодних ServiceHosts. Він насправді нічого не повертає, він просто управляє створенням, відкриттям та закриттям ServiceHosts внутрішньо. Я міг би обернути всі основні типи WCF, але це БАГАТО роботи, на які я насправді не мав повноважень. Також, як виявилось, проблема не спричинена паралельним виконанням, і все одно трапиться під час нормальної роботи. Я розпочав ще одне запитання тут на SO щодо проблеми, і, сподіваюся, отримаю відповідь.
jrista

@TrueWill: BTW, я взагалі не переживав, що ти пом'якшиш мої навички ... Я просто не хотів отримувати багато загальнодоступних відповідей, які охоплюють усі загальні речі про модульне тестування. Мені потрібна була швидка відповідь на цілком конкретну проблему. Вибачте, якщо я трохи зітхнувся, це не був мій намір. Я просто маю досить обмежений час, щоб привести цю роботу в дію.
jrista

3

Можливо, ви можете скористатись розширеним модульним тестуванням . Це дозволяє визначити послідовність, в якій ви запускаєте тест . Тож, можливо, доведеться створити новий файл cs для розміщення цих тестів.

Ось як ви можете зігнути методи тестування для роботи в потрібній послідовності.

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

Повідомте мене, чи це працює.


Чудова стаття. У мене насправді було зроблено закладку на CP. Дякуємо за посилання, але, як виявилося, проблема, здається, набагато глибша, оскільки, схоже, учасники тестів не виконують тести паралельно.
jrista

2
Зачекайте, спочатку ви говорите, що не хочете, щоб тест працював паралельно, а потім ви говорите, що проблема в тому, що учасники тесту не проводять тести паралельно ... так що це яке?
Graviton

Надане вами посилання більше не працює. І чи це щось ви можете зробити за допомогою xunit?
Аллен Ванг


0

Я додав атрибут [Collection ("Sequential")] в базовий клас:

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


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }

0

Жодна із запропонованих відповідей поки що не працювала для мене. У мене є основна програма dotnet із XUnit 2.4.1. Я домігся бажаної поведінки шляхом обхідного рішення, поставивши замість цього замок у кожному модульному тесті. У моєму випадку я не піклувався про порядок роботи, просто тести були послідовними.

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

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.