xUnit.net: Глобальна настройка + відключення?


98

Це питання стосується модульної системи тестування xUnit.net .

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

Як альтернатива, якщо я програмно викликаю xUnit, я також можу досягти бажаного за допомогою такого коду:

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

Хто-небудь може надати мені підказку про те, як декларативно або програмно запустити якийсь глобальний код налаштування / відключення?


1
Я думаю , ось відповідь: stackoverflow.com/questions/12379949 / ...
the_joric

Відповіді:


118

Наскільки мені відомо, xUnit не має глобальної точки розширення ініціалізації / відключення. Однак його легко створити. Просто створіть базовий тестовий клас, який реалізує, IDisposableта виконайте ініціалізацію в конструкторі, а також розірвіть у IDisposable.Disposeметоді. Це буде виглядати так:

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
}

Однак налаштування базового класу та код відключення будуть виконуватися для кожного виклику. Це може бути не тим, що ви хочете, оскільки це не дуже ефективно. Більш оптимізована версія використовує IClassFixture<T>інтерфейс, щоб гарантувати, що глобальна функція ініціалізації / відключення викликається лише один раз. У цій версії ви не розширюєте базовий клас з вашого тестового класу, а впроваджуєте IClassFixture<T>інтерфейс, де Tпосилається на ваш клас арматури:

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)
    {
    }
}

Це буде призводити до конструктору TestsFixtureтільки бути запущений один раз для кожного тестованого класу. Таким чином, це залежить від того, що саме ви хочете вибрати між двома методами.


4
Здається, IUseFixture більше не існує, замінивши її на IClassFixture.
GaTechThomas

9
Хоча це працює, я думаю, що CollectionFixture у відповіді від Гіра Сагберга більше підходить для цього сценарію, оскільки він був спеціально розроблений для цієї мети. Вам також не потрібно успадковувати свої тестові класи, ви просто позначаєте їх [Collection("<name>")]атрибутом
MichelZ

8
Чи є спосіб виконати налаштування асинхронізації та розірвати?
Андрій

Здається, що MS також застосували рішення IClassFixture. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim

3
XUnit пропонує три варіанти ініціалізації: для кожного методу тестування, для кожного тестового класу та охоплюючи кілька тестових класів. Документація знаходиться тут: xunit.net/docs/shared-context
GHN

48

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

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

Колекційні пристосування Коли використовувати: коли ви хочете створити один тестовий контекст і поділитися ним між тестами в декількох тестових класах, і забезпечити його очищення після закінчення всіх тестів у тестових класах.

Іноді вам захочеться поділитися об’єктом пристосування між кількома тестовими класами. Приклад бази даних, який використовується для пристосування класів, є чудовим прикладом: можливо, ви захочете ініціалізувати базу даних набором тестових даних, а потім залишити ці тестові дані на місці для використання кількома тестовими класами. Ви можете використовувати функцію приладу колекції xUnit.net, щоб поділитися одним екземпляром об’єкта серед тестів у декількох тестових класах.

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

Створіть клас арматури та помістіть код запуску в конструктор класу арматури. Якщо класу приладів потрібно виконати очищення, застосуйте IDisposable у класі пристосування та вставте код очищення в метод Dispose (). Створіть клас визначення колекції, прикрасивши його атрибутом [CollectionDefinition], давши йому унікальну назву, яка буде ідентифікувати тестову колекцію. Додайте ICollectionFixture <> до класу визначення колекції. Додайте атрибут [Collection] до всіх тестових класів, які будуть частиною колекції, використовуючи унікальне ім’я, яке ви вказали в атрибуті [CollectionDefinition] класу визначення тестової колекції. Якщо тестові класи потребують доступу до екземпляра приладу, додайте його як аргумент конструктора, і він буде наданий автоматично. Ось простий приклад:

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 трактує колективні прилади приблизно так само, як і пристосування класів, за винятком того, що термін служби об’єкта приладдя колекції довший: він створюється до запуску будь-яких тестів у будь-якому з тестових класів у колекції, і не буде очищений до тих пір, поки всі тестові класи в колекції не закінчаться.

Тестові колекції також можна прикрасити IClassFixture <>. xUnit.net розглядає це так, ніби кожен окремий тестовий клас у колекції тестів був прикрашений пристосуванням класу.

Збірники тестів також впливають на те, як xUnit.net запускає тести при їх паралельному запуску. Для отримання додаткової інформації див. Запуск тестів паралельно.

Важливе зауваження.


1
"Тестові колекції також можна прикрасити IClassFixture <>. XUnit.net розглядає це так, ніби кожен окремий тестовий клас у тестовій колекції був прикрашений кріпленням класу." Будь-який шанс, що я можу отримати приклад цього? Я не зовсім це розумію.
rtf

@TannerFaulkner Пристосування класу - це спосіб налаштування рівня класу та його розірвання, як у випадку з традиційним тестовим проектом .net, коли у вас є метод Ініціалізація тесту: [TestInitialize] public void Initialize () {
Ларрі Сміт,

Єдина проблема, з якою я маю з цим, полягає в тому, що вам потрібно прикрасити свої тестові класи Collectionатрибутом, щоб відбулася "глобальна" настройка. Це означає, що якщо у вас є що-небудь, що вам потрібно налаштувати до запуску -any- test, вам потрібно прикрасити -all-test класи цим атрибутом. На мою думку, це занадто крихко, оскільки забуття прикрасити один тестовий клас може призвести до помилок, які важко відстежити. Було б непогано, якби xUnit створив спосіб по-справжньому глобального налаштування та розірвання.
Зодман

13

Є просте легке рішення. Використовуйте плагін Fody.ModuleInit

https://github.com/Fody/ModuleInit

Це nuget-пакет, і при його встановленні він додає новий файл, викликаний ModuleInitializer.csдо проекту. Тут є один статичний метод, який вбудовується в збірку після збірки і запускається, як тільки збірка завантажується і перед тим, як щось запускати.

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

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

і всі тести, розміщені в цій збірці, матимуть правильну розблоковану ліцензію для них.


2
Солідна ідея; на жаль, поки це не працює з модульними тестами DNX.
Джефф Данлоп,

12

Щоб поділитися SetUp / TearDown-кодом між кількома класами, ви можете використовувати колекцію XUnit's .

Цитата:

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

  • Створіть клас арматури та помістіть код запуску в конструктор класу арматури.
  • Якщо класу приладів потрібно виконати очищення, застосуйте IDisposable у класі пристосування та вставте код очищення в метод Dispose ().
  • Створіть клас визначення колекції, прикрасивши його атрибутом [CollectionDefinition], давши йому унікальну назву, яка буде ідентифікувати тестову колекцію.
  • Додайте ICollectionFixture <> до класу визначення колекції.
  • Додайте атрибут [Collection] до всіх тестових класів, які будуть частиною колекції, використовуючи унікальне ім’я, яке ви вказали в атрибуті [CollectionDefinition] класу визначення тестової колекції.
  • Якщо тестові класи потребують доступу до екземпляра приладу, додайте його як аргумент конструктора, і він буде наданий автоматично.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.