Как работает scope тестового контекста в xUnit
Что проверяют
Знаете ли вы главное отличие xUnit от NUnit и MSTest: экземпляр тестового класса создаётся заново для каждого теста. Из этого следуют правила изоляции, шаринга дорогих ресурсов и параллельного запуска.
Как отвечать
- На каждый тестовый метод создаётся новый объект класса: конструктор играет роль подготовки,
IDisposable.Disposeроль уборки. Атрибутов[SetUp]и[TearDown]нет намеренно, чтобы состояние не текло между тестами через поля. - Асинхронная подготовка через
IAsyncLifetime(InitializeAsync,DisposeAsync): конструктор асинхронным быть не может. - Один объект на класс тестов:
IClassFixture<T>, создаётся один раз и приходит параметром конструктора. Один объект на несколько классов:ICollectionFixture<T>плюс атрибут[Collection("Db")]на каждом. В xUnit v3 добавился уровень сборки:[assembly: AssemblyFixture(typeof(T))]. - Параллельность привязана к коллекциям: по умолчанию каждый класс это своя коллекция, разные коллекции идут параллельно, тесты внутри одной последовательно. Объединение классов в коллекцию заодно сериализует доступ к общему ресурсу.
- Вывод только через
ITestOutputHelperиз конструктора:Console.WriteLineв параллельном прогоне теряется. В v3 появилсяTestContext.Currentс текущим тестом и токеном отмены.
public class OrdersTests : IClassFixture<DbFixture>
{
private readonly DbFixture _db;
public OrdersTests(DbFixture db) => _db = db;
}Где спотыкаются
Ищут настройку области видимости вроде Class или Method. Такой опции нет: жизненный цикл фиксирован, выбор делается тем, берёте вы поле класса или фикстуру.
Хранят изменяемое состояние в статике или в фикстуре и получают зависимость от порядка тестов. Фикстура живёт дольше теста, писать в неё общие данные нельзя.
Ждут, что Dispose фикстуры сработает после каждого теста. Он сработает один раз, когда отработают все тесты класса или коллекции.
Не учитывают параллельность и делят между классами базу или каталог на диске без общей коллекции. Тесты падают через раз, а причину ищут в коде приложения.