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
49

Я искал тот же ответ, и в настоящее время документация 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 создал способ действительно глобальной настройки и демонтажа.
Zodman
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 CollectionFixture .

Цитата:

Чтобы использовать приспособления для сбора, необходимо выполнить следующие действия:

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