11

단위 테스트가 종속성에 대해 컨테이너를 호출하는 데 의존하지 않도록하려고합니다. <T>()종속성 주입을 용이하게하기 위해 매개 변수없는 생성자가없는 .NET 단위 테스트

것은 나는 현재 AutoFac 2.2.4을 사용하고 xUnit.NETNUnit과했지만, 모두 이 문제이있어 :이 객체

에 대해 정의

없음 매개 변수가없는 생성자를

어떻게이 문제를 해결할 수 있습니까? 이것을 지원할 특별한 단위 테스팅 프레임 워크인가, 아니면 단지 프레임 워크가 어떻게 구성되어 있는가?

이 작업을 수행하지 않아야합니까? 아니면 단지 의존성이있는 생성자와 함께 작동하도록 테스트 클래스를 설정할 수 있습니까?

여기에 코드의 일부는 다음과 같습니다

public class ProductTests : BaseTest 
{ 
    readonly private IProductRepository _repo; 

    public ProductTests(IProductRepository r) 
    { 
     _repo = r; 
    } 

    //working unit tests here with default constructor 
} 

내가 기본 클래스 생성자에서 잘못 컨테이너를 초기화를 선택 했습니까?

public abstract class BaseTest 
{ 
    protected BaseTest() 
    { 
     var builder = new ContainerBuilder(); 
     builder.RegisterType<ProductRepository>().As<IProductRepository>(); 
     builder.Build(); 
    } 
} 
+1

테스트 클래스에 생성자가 필요한 이유는 무엇입니까? "주입"을 설정 방법에 넣습니다. –

답변

11

초기 문제는 실제로 테스트 프레임 워크의 설계 방식 때문입니다. 테스트 인스턴스를 인스턴스화하기 위해 매개 변수없는 생성자가 필요합니다. 그리고 정당하게. 이러한 프레임 워크에서는 생성자가 테스트 초기화에 의존해서는 안됩니다. 그것이 SetUp 메소드의 목적입니다. 결과적으로 테스트 클래스 자체는 주입에 적합하지 않습니다.

IMO는 컨테이너를 사용하지 않도록 테스트를 개발할 때 별다른 문제가되지 않습니다. 결국 각 테스트 클래스는 하나의 "테스트중인 시스템"(SUT)에 집중해야합니다. 왜 설치 방법으로 해당 시스템을 직접 인스턴스화하고 각 종속성을 제공하지 않는가? (보통 가짜 형태로)? 이 방법을 사용하면 테스트에서 불필요한 종속성, 즉 IoC 프레임 워크를 효과적으로 제거 할 수 있습니다.

내 테스트에서 IoC 프레임 워크를 사용하는 유일한 경우는 "컨테이너 테스트"입니다. 이 테스트는 컨테이너가 application or assembly modules으로 초기화 된 후 컨테이너에서 특정 서비스를 확인할 수 있는지 확인하는 데 중점을 둡니다.

+0

+1. 나는 Peter와 완전히 동의합니다. 테스트 방법에서는 컨테이너를 사용하지 말고 수동으로 (또는 조롱 프레임 워크를 사용하여) SUT를 만듭니다. – Steven

+1

+1 또한 - 구성 요소가 너무 많아서 테스트 픽스처에서 손으로 편하게 만들 수없는 경우 너무 많은 책임이 있습니다. –

+0

Peter, Steven 및 Nicholas에게 왜이 접근법이 필요하지 않은지 추론 해 주신 것에 대해 감사드립니다. 나는이 접근법이 쉽게 벗어나 "단위"경계를 초과 할 수있는 테스트를 만드는 것을 지원할 수 있다는 데 동의한다. –

4

캡슐화했지만 Autofac에 대한 테스트가 허용됩니다. 모든 TestFixtures는 다음과 같이 정의 된 Fixture에서 상속받습니다.

public class Fixture 
{ 
    private static readonly IContainer MainContainer = Ioc.Build(); 
    private readonly TestLifetime _testLifetime = new TestLifetime(MainContainer); 

    [SetUp] 
    public void SetUp() 
    { 
     _testLifetime.SetUp(); 
    } 

    [TearDown] 
    public void TearDown() 
    { 
     _testLifetime.TearDown(); 
    } 

    protected TService Resolve<TService>() 
    { 
     return _testLifetime.Resolve<TService>(); 
    } 

    protected void Override(Action<ContainerBuilder> configurationAction) 
    { 
     _testLifetime.Override(configurationAction); 
    } 
} 

public class TestLifetime 
{ 
    private readonly IContainer _mainContainer; 

    private bool _canOverride; 
    private ILifetimeScope _testScope; 

    public TestLifetime(IContainer mainContainer) 
    { 
     _mainContainer = mainContainer; 
    } 

    public void SetUp() 
    { 
     _testScope = _mainContainer.BeginLifetimeScope(); 
     _canOverride = true; 
    } 

    public void TearDown() 
    { 
     _testScope.Dispose(); 
     _testScope = null; 
    } 

    public TService Resolve<TService>() 
    { 
     _canOverride = false; 
     return _testScope.Resolve<TService>(); 
    } 

    public void Override(Action<ContainerBuilder> configurationAction) 
    { 
     _testScope.Dispose(); 

     if (!_canOverride) 
      throw new InvalidOperationException("Override can only be called once per test and must be before any calls to Resolve."); 

     _canOverride = false; 
     _testScope = _mainContainer.BeginLifetimeScope(configurationAction); 
    } 
}