xUnit.net: Глобальная настройка + разборка?

Этот вопрос о платформе модульного тестирования xUnit.net.

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

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

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

Может ли кто-нибудь дать мне подсказку о том, как декларативно или программно выполнить какой-то глобальный код установки / разрыва?

6 ответов

Решение

Насколько я знаю, 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 void SetFixture(TestsFixture data)
    {
    }
}

Это приведет к конструктору TestsFixture выполняется только один раз для каждого тестируемого класса. Таким образом, это зависит от того, что именно вы хотите выбрать между двумя методами.

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

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

Приспособления коллекции Когда использовать: когда вы хотите создать один контекст теста и поделиться им среди тестов в нескольких классах тестов и очистить его после завершения всех тестов в классах тестов.

Иногда вы захотите поделиться объектом фикстуры между несколькими тестовыми классами. Отличным примером является пример базы данных, используемой для фиксаций классов: вы можете инициализировать базу данных набором тестовых данных, а затем оставить эти тестовые данные на месте для использования несколькими тестовыми классами. Вы можете использовать функцию фиксации коллекции xUnit.net для совместного использования одного экземпляра объекта среди тестов в нескольких тестовых классах.

Чтобы использовать коллекционные светильники, вам необходимо выполнить следующие шаги:

Создайте класс fixture и поместите код запуска в конструктор класса fixture. Если классу фикстур необходимо выполнить очистку, реализуйте IDisposable для класса фикстуры и поместите код очистки в метод Dispose(). Создайте класс определения коллекции, украсив его атрибутом [CollectionDefinition], присвоив ему уникальное имя, которое будет идентифицировать тестовую коллекцию. Добавьте ICollectionFixture<> в класс определения коллекции. Добавьте атрибут [Collection] ко всем тестовым классам, которые будут частью коллекции, используя уникальное имя, которое вы указали в атрибуте [CollectionDefinition] класса определения тестовой коллекции. Если тестовым классам нужен доступ к экземпляру fixture, добавьте его в качестве аргумента конструктора, и он будет предоставлен автоматически. Вот простой пример:

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 выполняет тесты при их параллельном запуске. Для получения дополнительной информации см. Выполнение параллельных тестов.

Важное примечание: светильники должны быть в той же сборке, что и тест, в котором они используются.

Есть легкое простое решение. Используйте плагин 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");
    }
}

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

Для совместного использования SetUp/TearDown-кода между несколькими классами вы можете использовать коллекцию xUnit CollectionFixture.

Цитата:

Чтобы использовать коллекционные светильники, вам необходимо выполнить следующие шаги:

  • Создайте класс fixture и поместите код запуска в конструктор класса fixture.
  • Если классу фикстур необходимо выполнить очистку, реализуйте IDisposable для класса фикстуры и поместите код очистки в метод Dispose().
  • Создайте класс определения коллекции, украсив его атрибутом [CollectionDefinition], присвоив ему уникальное имя, которое будет идентифицировать тестовую коллекцию.
  • Добавьте ICollectionFixture<> в класс определения коллекции.
  • Добавьте атрибут [Collection] ко всем тестовым классам, которые будут частью коллекции, используя уникальное имя, которое вы указали в атрибуте [CollectionDefinition] класса определения тестовой коллекции.
  • Если тестовым классам нужен доступ к экземпляру fixture, добавьте его в качестве аргумента конструктора, и он будет предоставлен автоматически.

Если у вас есть одна функция глобальной инициализации и глобальной очистки, вы можете написать такой класс:

[CollectionDefinition("TestEngine")]
public class TestEngineInitializer: IDisposable, ICollectionFixture<TestEngineInitializer>
{
    public TestEngineInitializer()
    {
        MyOwnTestEngine.Init();
    }

    public void Dispose()
    {
        MyOwnTestEngine.Cleanup();
    }
}

И для каждого тестового класса, где требуется выполнить эту инициализацию, нужно добавить дополнительный атрибут:

[Collection("TestEngine")]
public class MyTests
{

Важно: имена, используемые в Collection и CollectionDefinition-атрибуты должны совпадать.

Вы также можете использовать экземпляр класса TestEngine в конструкторе, например, следующим образом:

[Collection("TestEngine")]
public class MyTests
{
     public MyTests(TestEngineInitializer initializer)
     {
     }

но это не обязательно.

Вся документация по проблеме находится здесь:

https://xunit.net/docs/shared-context

Я решил эту проблему так:

      public class IntegrationTest: IClassFixture<CustomWebApplicationFactory<Startup>>
{
    private readonly CustomWebApplicationFactory<Startup> _factory;
    public IntegrationTest(CustomWebApplicationFactory<Startup> factory)
    {
        CleanData();
        Initdata();
        _factory = factory;
    }
    public static void Initdata()
    {
        using (var context = new DataContext())
        {
            //Add Data
        }
    }

    public void CleanData()
    {
        using (var context = new DataContext())
        {
            //Clean Data
        }
    }
}

Супер легко. это работает очень хорошо. может решить вашу :)

Другие вопросы по тегам