[c#] xUnit.net의 모든 테스트 전후에 한 번 코드 실행

TL; DR-MSTest의 xUnit에 해당하는 기능을 찾고 있습니다 AssemblyInitialize(내가 좋아하는 하나의 기능).

특히 다른 종속성없이 실행할 수있는 Selenium 연기 테스트가 있기 때문에 찾고 있습니다. 나는 나를 위해 IisExpress를 시작하고 처분 할 때 죽일 Fixture가 있습니다. 그러나 모든 테스트 전에 이것을 수행하면 런타임이 엄청나게 부풀어집니다.

테스트를 시작할 때이 코드를 한 번 트리거하고 마지막에 폐기 (프로세스 종료)하고 싶습니다. 어떻게하면 되나요?

“현재 실행중인 테스트 수”와 같은 것에 프로그래밍 방식으로 액세스 할 수 있어도 뭔가 알아낼 수 있습니다.



답변

2015 년 11 월부터 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;
        }
    }

AssemblyInitialize어떤 테스트 컬렉션이 속하는지 각 테스트 클래스에 선언해야하기 때문에 MsTests보다 조금 더 장황 하지만 모듈화도 더 가능합니다.

참고 : 샘플은 설명서 에서 가져 왔습니다 .


답변

정적 필드를 만들고 종료자를 구현합니다.

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테스트에서 사용하여 조명기에 액세스합니다 .


답변

어셈블리 초기화에서 코드를 실행하려면 다음을 수행 할 수 있습니다 (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참조하십시오.


답변

오늘날 프레임 워크에서는 불가능합니다. 이것은 2.0에 계획된 기능입니다.

2.0 이전에이 작업을 수행하려면 프레임 워크에서 중요한 재 아키텍처를 수행하거나 고유 한 특성을 인식하는 자체 러너를 작성해야합니다.


답변

내가 사용 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  
  }
}


답변

모든 xUnit 테스트가 끝날 때 실행할 수있는 옵션이 없어서 상당히 짜증이났습니다. 여기에있는 일부 옵션은 모든 테스트를 변경하거나 하나의 컬렉션에 넣는 (동 기적으로 실행됨을 의미) 포함하기 때문에 그다지 좋지 않습니다. 그러나 Rolf Kristensen의 대답은이 코드를 얻는 데 필요한 정보를 제공했습니다. 약간 길지만 테스트 프로젝트에 추가하기 만하면되고 다른 코드 변경은 필요하지 않습니다.

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에서 상속하고 메시지 싱크를 래핑하면됩니다.


답변

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; }
}

편집 : 새 조명기가 각 테스트 클래스에 대해 생성하는 문제를 수정합니다.