Выполните код один раз до и после ВСЕХ тестов в xUnit.net
TL; DR - Я ищу xUnit эквивалент MSTest AssemblyInitialize
(он же имеет функцию ONE, которая мне нравится).
В частности, я ищу его, потому что у меня есть тесты на селен, которые я бы хотел запустить без каких-либо других зависимостей. У меня есть Fixture, который запустит IisExpress для меня и убьет его при утилизации. Но делать это перед каждым тестом очень быстро вздувается.
Я хотел бы запустить этот код один раз в начале тестирования и избавиться от него (завершение процесса) в конце. Как я мог это сделать?
Даже если я могу получить программный доступ к чему-то вроде "сколько тестов в настоящее время выполняется", я могу что-то понять.
Ответы
Ответ 1
По состоянию на ноябрь 2015 года xUnit 2 отсутствует, поэтому существует канонический способ обмена функциями между тестами. Это описано здесь.
В основном вам нужно создать класс, выполняющий прибор:
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
. Этот класс позволяет Xunit создавать тестовую коллекцию и будет использовать данное приспособление для всех тестовых классов коллекции.
[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;
}
}
Это немного более подробный, чем MsTests AssemblyInitialize
поскольку вам нужно объявить на каждом тестовом классе, к которому принадлежит тестовая коллекция, но он также более модулем (и с помощью MsTests вам все равно нужно поместить TestClass на свои классы)
Примечание: образцы взяты из документации.
Ответ 2
Создайте статическое поле и реализуйте финализатор.
Вы можете использовать тот факт, что xUnit создает AppDomain для запуска вашей тестовой сборки и выгружает ее по завершении. Разгрузка домена приложения приведет к запуску финализатора.
Я использую этот метод для запуска и остановки IISExpress.
public sealed class ExampleFixture
{
public static ExampleFixture Current = new ExampleFixture();
private ExampleFixture()
{
// Run at start
}
~ExampleFixture()
{
Dispose();
}
public void Dispose()
{
GC.SuppressFinalize(this);
// Run at end
}
}
Изменение: получить доступ к прибору, используя ExampleFixture.Current
в ваших тестах.
Ответ 3
Сегодня это невозможно сделать в рамках. Это функция, запланированная для 2.0.
Чтобы выполнить эту работу до 2.0, вам потребуется выполнить значительную переструктурированную архитектуру в структуре или написать собственные бегуны, которые распознают ваши собственные специальные атрибуты.
Ответ 4
Чтобы выполнить код при инициализации сборки, можно сделать это (протестировано с xUnit 2.3.1)
using Xunit.Abstractions;
using Xunit.Sdk;
[assembly: Xunit.TestFramework("MyNamespace.MyClassName", "MyAssemblyName")]
namespace MyNamespace
{
public class MyClassName : XunitTestFramework
{
public MyClassName(IMessageSink messageSink)
:base(messageSink)
{
// Place initialization code here
}
public new void Dispose()
{
// Place tear down code here
base.Dispose();
}
}
}
Смотрите также https://github.com/xunit/samples.xunit/tree/master/AssemblyFixtureExample
Ответ 5
Я использую AssemblyFixture (NuGet).
Что он делает - он предоставляет интерфейс IAssemblyFixture<T>
, который заменяет любой IClassFixture<T>
, где вы хотите, чтобы время жизни объекта было как тестовая сборка.
Пример:
public class Singleton { }
public class TestClass1 : IAssemblyFixture<Singleton>
{
readonly Singletone _Singletone;
public TestClass1(Singleton singleton)
{
_Singleton = singleton;
}
[Fact]
public void Test1()
{
//use singleton
}
}
public class TestClass2 : IAssemblyFixture<Singleton>
{
readonly Singletone _Singletone;
public TestClass2(Singleton singleton)
{
//same singleton instance of TestClass1
_Singleton = singleton;
}
[Fact]
public void Test2()
{
//use singleton
}
}
Ответ 6
Предоставляет ли ваш инструмент построения такую функцию?
В мире Java при использовании Maven в качестве инструмента построения мы используем соответствующий фазы жизненного цикла сборки. Например. в вашем случае (приемочные испытания с помощью инструментов, похожих на селен), можно эффективно использовать фазы pre-integration-test
и post-integration-test
для запуска/остановки webapp до/после одного integration-test
s.
Я уверен, что в вашей среде можно настроить тот же механизм.
Ответ 7
Вы можете использовать интерфейс IUseFixture, чтобы это произошло. Также все ваши тесты должны наследовать класс TestBase. Вы также можете использовать OneTimeFixture непосредственно из своего теста.
public class TestBase : IUseFixture<OneTimeFixture<ApplicationFixture>>
{
protected ApplicationFixture Application;
public void SetFixture(OneTimeFixture<ApplicationFixture> data)
{
this.Application = data.Fixture;
}
}
public class ApplicationFixture : IDisposable
{
public ApplicationFixture()
{
// This code run only one time
}
public void Dispose()
{
// Here is run only one time too
}
}
public class OneTimeFixture<TFixture> where TFixture : new()
{
// This value does not share between each generic type
private static readonly TFixture sharedFixture;
static OneTimeFixture()
{
// Constructor will call one time for each generic type
sharedFixture = new TFixture();
var disposable = sharedFixture as IDisposable;
if (disposable != null)
{
AppDomain.CurrentDomain.DomainUnload += (sender, args) => disposable.Dispose();
}
}
public OneTimeFixture()
{
this.Fixture = sharedFixture;
}
public TFixture Fixture { get; private set; }
}
РЕДАКТИРОВАТЬ: Устранить проблему, которую создает новый прибор для каждого тестового класса.
Ответ 8
Я был довольно раздражен тем, что у меня не было возможности выполнить все в конце всех тестов xUnit. Некоторые параметры здесь не так хороши, так как они включают в себя изменение всех ваших тестов или помещение их в одну коллекцию (то есть они выполняются синхронно). Но ответ рольфа Кристенсена дал мне необходимую информацию, чтобы добраться до этого кода. Это немного долго, но вам нужно только добавить его в ваш тестовый проект, никаких других изменений кода не требуется:
using Siderite.Tests;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
[assembly: TestFramework(
SideriteTestFramework.TypeName,
SideriteTestFramework.AssemblyName)]
namespace Siderite.Tests
{
public class SideriteTestFramework : ITestFramework
{
public const string TypeName = "Siderite.Tests.SideriteTestFramework";
public const string AssemblyName = "Siderite.Tests";
private readonly XunitTestFramework _innerFramework;
public SideriteTestFramework(IMessageSink messageSink)
{
_innerFramework = new XunitTestFramework(messageSink);
}
public ISourceInformationProvider SourceInformationProvider
{
set
{
_innerFramework.SourceInformationProvider = value;
}
}
public void Dispose()
{
_innerFramework.Dispose();
}
public ITestFrameworkDiscoverer GetDiscoverer(IAssemblyInfo assembly)
{
return _innerFramework.GetDiscoverer(assembly);
}
public ITestFrameworkExecutor GetExecutor(AssemblyName assemblyName)
{
var executor = _innerFramework.GetExecutor(assemblyName);
return new SideriteTestExecutor(executor);
}
private class SideriteTestExecutor : ITestFrameworkExecutor
{
private readonly ITestFrameworkExecutor _executor;
private IEnumerable<ITestCase> _testCases;
public SideriteTestExecutor(ITestFrameworkExecutor executor)
{
this._executor = executor;
}
public ITestCase Deserialize(string value)
{
return _executor.Deserialize(value);
}
public void Dispose()
{
_executor.Dispose();
}
public void RunAll(IMessageSink executionMessageSink, ITestFrameworkDiscoveryOptions discoveryOptions, ITestFrameworkExecutionOptions executionOptions)
{
_executor.RunAll(executionMessageSink, discoveryOptions, executionOptions);
}
public void RunTests(IEnumerable<ITestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
{
_testCases = testCases;
_executor.RunTests(testCases, new SpySink(executionMessageSink, this), executionOptions);
}
internal void Finished(TestAssemblyFinished executionFinished)
{
// do something with the run test cases in _testcases and the number of failed and skipped tests in executionFinished
}
}
private class SpySink : IMessageSink
{
private readonly IMessageSink _executionMessageSink;
private readonly SideriteTestExecutor _testExecutor;
public SpySink(IMessageSink executionMessageSink, SideriteTestExecutor testExecutor)
{
this._executionMessageSink = executionMessageSink;
_testExecutor = testExecutor;
}
public bool OnMessage(IMessageSinkMessage message)
{
var result = _executionMessageSink.OnMessage(message);
if (message is TestAssemblyFinished executionFinished)
{
_testExecutor.Finished(executionFinished);
}
return result;
}
}
}
}
Основные моменты:
- Сборка: TestFramework инструктирует xUnit использовать ваш фреймворк, который
прокси по умолчанию
- SideriteTestFramework также оборачивает исполнителя в пользовательский класс
затем оборачивает приемник сообщений
- в конце выполняется метод Finished со списком тестов
запустить и результат из сообщения xUnit
Больше работы можно сделать здесь. Если вы хотите выполнить что-либо, не заботясь о выполнении тестов, вы можете унаследовать XunitTestFramework и просто обернуть приемник сообщений.