Подтвердить что ты не робот

Выполните код один раз до и после ВСЕХ тестов в xUnit.net

TL; DR - Я ищу xUnit эквивалент MSTest AssemblyInitialize (он же имеет функцию ONE, которая мне нравится).

В частности, я ищу его, потому что у меня есть тесты на селен, которые я бы хотел запустить без каких-либо других зависимостей. У меня есть Fixture, который запустит IisExpress для меня и убьет его при утилизации. Но делать это перед каждым тестом очень быстро вздувается.

Я хотел бы запустить этот код один раз в начале тестирования и избавиться от него (завершение процесса) в конце. Как я мог это сделать?

Даже если я могу получить программный доступ к чему-то вроде "сколько тестов в настоящее время выполняется", я могу что-то понять.

4b9b3361

Ответ 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 и просто обернуть приемник сообщений.