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


80

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

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

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

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


xUnit을 "JUnit, NUnit 등과 같은 언어 별 단위 테스트 도구의 일반적인 그룹"으로 의미합니까? 또는 "xUnit.net, .Net 단위 테스트 도구"로 xUnit?
Andy Tinkham

3
이 테이블을 기반으로 xunit.codeplex.com/… 이에 상응하는 것이 있다고 생각하지 않습니다. 해결 방법은 어셈블리 초기화를 싱글 톤으로 이동하고 각 생성자에서 호출하는 것입니다.
allen

@allen-내가하는 일과 비슷하지만 어셈블리 분해없이 어셈블리 이니셜 라이저를 제공합니다. 이것이 내가 테스트 횟수에 대해 묻는 이유입니다.
George Mauer

답변:


59

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보다 조금 더 장황 하지만 모듈화도 더 가능합니다.

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


10
내가 이것을 읽을 때 : "... //이 클래스에는 코드가 없으며 생성되지 않았습니다 ..."그러면 Microsoft의 AssemblyInitialize 구현을 선호합니다. 훨씬 더 우아합니다.
Elisabeth

나는 조금 이상한 동의
gwenzek

8
@Elisabeth 방금 이것을 사용하고 [CollectionDefinition("Database collection")]속성과 ICollectionFixture<DatabaseFixture>인터페이스를 DatabaseFixture클래스에 추가했으며 모든 것이 작동합니다. 빈 클래스를 제거하고 나에게 더 깨끗해 보입니다!
Christian Droulers

11
[Collection] 속성은 테스트가 병렬로 실행되는 것을 방지합니다. 테스트를 병렬로 실행할 수있는 xUnit의 전역 초기화 / 해체에 대한 다른 접근 방식이 있습니까?
IT Hit WebDAV

1
이것이 오래되었다는 것을 알고 있지만 단일 테스트 클래스에 여러 컬렉션을 어떻게 가지고 있습니까? 데이터베이스 설정 코드와 매핑 코드를 실행하고 싶습니다.
rball

25

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

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


흥미 롭습니다-appdomain이 즉시 폐기됩니까 아니면 시간이 걸리나요? 즉, 두 번의 테스트가 연속적으로 실행되면 어떻게됩니까?
George Mauer 2013

어셈블리의 마지막 테스트 실행이 완료된 직후 AppDomain이 언로드됩니다. 동일한 테스트 어셈블리에서 연속적으로 실행되는 100 개의 테스트가있는 경우 100 번째 테스트가 완료된 후 언로드됩니다.
Jared Kells 2013

1
이 방법을 사용하여 테스트를 시작할 때 IISExpress를 시작하고 모든 테스트가 완료된 후 중지합니다. ReSharper 및 Teamcity의 MSBuild에서 잘 작동합니다.
Jared Kells 2013

1
@GeorgeMauer와 Jared, 아마도 AppDomain Unload이벤트가 더 유용할까요? (물론 셧다운 중에는 모든 베팅이 해제되지만 메모리에서 조금 더 안정적 일 수 있습니다.)
Ruben Bartelink

2
이것은 일화 일화이지만이 답변을 게시 한 이후로 내 빌드 서버는 한 달 동안 실행 되었으며이 방법을 사용하여 300 개의 빌드를 수행했으며 정상적으로 작동합니다.
Jared Kells 2013 년

21

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


안녕하세요, 흥미로워 보입니다. 더 많은 정보를 얻을 수 있습니까?
Jonatha ANTOINE 2019-08-12

1
@JonathaANTOINE 가능한 답변이 10000 개있는 열린 질문으로 시작하는 대신 새로운 스택 오버플로 질문을 만들어야한다고 생각합니다.
Rolf Kristensen

좋아, 나는 이것을하는 방법을 알아 냈고 나는 들어오는 블로그 포스트가있다 :)
Jonatha ANTOINE aug

생성자가 호출되지만 dispose는 테스트 케이스 후에 호출되지 않습니다. (xUnit 2.4.1)
TonyQ

16

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

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


감사합니다 브래드, 내가 여기있는 한 VS 테스트 러너에 대한 가장 최근의 질문에 대한 아이디어가 있습니까? visualstudiogallery.msdn.microsoft.com/…
George Mauer

1
안녕 Brad-아래 @JaredKells의 답변을 확인하십시오. 그 접근 방식에 문제가 있습니까?
George Mauer 2013

1
.NET 프레임 워크에 따르면 종료자가 언제 실행 될지 또는 전혀 실행 될지에 대한 보장이 없습니다. 그게 마음에 든다면 그의 제안은 괜찮은 것 같아요. :)
Brad Wilson

5
이제 2.0이 출시되었으므로 모든 테스트에 대해 공통 기본 클래스를 가질 필요가없는 솔루션 (또는 일부 공통 코드 호출)은 XunitTestFramework의 자체 하위 클래스를 구현하고 생성자에서 초기화를 수행하는 것입니다. 종료 자 및 TestFrameworkAttribute로 어셈블리를 표시합니다. 종료 자 대신 사용할 수있는 방법은 정리를 수행하는 기본 클래스 TestFramework의 보호 된 DisposalTracker 속성에 개체를 추가하는 것입니다.
Avi Cherry

1
@AviCherry, 이제 원래 질문에 대한 답이되어야합니다. 나를 위해
훌륭하게

3

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

내가 틀렸다 그러나이 있음을 언급 그 가치가 2 + xUnit의 경우 정정 해줘
조지 마우어

[assembly: TestFramework("Xunit.Extensions.Ordering.TestFramework", "Xunit.Extensions.Ordering")]프로젝트의 클래스 중 하나에 추가하지 않으면 작동하지 않습니다 . 참조
manjeet lama 201

2

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


1

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

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


이것은 잘못된 조언이자 나쁜 조언입니다. 의 메소드는 ApplicationFixture단 한 번이 아니라 각 테스트 전후에 실행됩니다. TestBase하나의 상속 링크를 사용 하는 것과 동일하게 항상 상속하는 것도 좋지 않으며 더 이상 관련 클래스 그룹간에 공통 메서드를 공유하는 데 사용할 수 없습니다. 사실, 그것이 바로 IUseFixture상속에 의존 할 필요가없는 발명 된 이유 입니다. 마지막으로, xUnit의 제작자는 2.0이 출시 될 때까지 제대로 수행 할 수 없다는 질문에 대한 대답을 이미 받아 들였습니다.
George Mauer

실제로 각 테스트 전에 실행되지 않습니다. IUseFixture를 사용할 때 Fixture는 각 유형의 테스트 클래스에 대해 한 번만 생성합니다. 따라서 코드를 Fixture 생성자에 넣으면 한 번만 실행됩니다. 나쁜 점은 각 유형의 테스트 클래스에 대해 한 번 실행된다는 것입니다. 나는 그것이 각 테스트 세션에 대해 하나의 인스턴스를 만들 것이라고 생각했지만 그렇지 않습니다. 이 문제를 해결하기 위해 샘플 코드를 수정합니다. 정적 변수를 사용하여 Fixture 인스턴스를 저장하여 한 번만 생성되도록하고 AppDomain Unload 이벤트를 사용하여 Fixture를 처리합니다.
Khoa Le

0

빌드 도구가 이러한 기능을 제공합니까?

Java 세계에서 Maven 을 빌드 도구로 사용할 때 빌드 수명주기 의 적절한 단계를 사용합니다 . 예를 들어 귀하의 경우 (Selenium과 유사한 도구로 수용 테스트) pre-integration-testpost-integration-test단계를 잘 사용 하여 웹 응용 프로그램 을 시작 / 중지 할 수 integration-test있습니다.

귀하의 환경에서 동일한 메커니즘을 설정할 수 있다고 확신합니다.


완전한 빌드 시스템으로 모든 것이 가능합니다. 나는 이것을 psake 또는 grunt로 상당히 쉽게 설정할 수 있습니다. 문제는 Visual Studio 통합 테스트 실행기가 단순히 테스트를 실행하기 위해 빌드 시스템을 사용하지 않는다는 것입니다. 내가 본 코드베이스에서 IDE에서 직접 호출하고 dll을 직접 실행합니다.
George Mauer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.