문제

최신 ASP.NET MVC 프로젝트를 구성하면서 단위 테스트, 종속 주입 및 모든 재즈에 들어가기 시작했습니다.

나는 지금 내 컨트롤러를 테스트하고 싶은 시점에 이르기까지 IOC 컨테이너없이이를 수행하는 방법을 파악하는 데 어려움을 겪고 있습니다.

예를 들어 간단한 컨트롤러를 사용하십시오.

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository = new SqlQuestionsRepository();

    // ... Continue with various controller actions
}

이 클래스는 sqlquestionsrepository의 직접적인 인스턴스화로 인해 단위 테스트 가능하지 않습니다. 따라서 종속 주입 경로를 내려 가서 다음을 수행하겠습니다.

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository;

    public QuestionsController(IQuestionsRepository repository)
    {
        _repository = repository;
    }
}

이것은 더 좋아 보인다. 이제 Mock iQuestionsRepository로 단위 테스트를 쉽게 작성할 수 있습니다. 그러나 지금 컨트롤러를 인스턴스화하려는 것은 무엇입니까? 어딘가에 통화 체인 sqlquestionrepository는 인스턴스화해야합니다. 나는 단순히 다른 곳에서 문제를 단순히 바꾸는 것처럼 보인다.

이제 저는 이것이 IOC 컨테이너가 컨트롤러 의존성을 배선하여 동시에 컨트롤러를 쉽게 테스트 할 수 있도록하는 동시에 IOC 컨테이너가 귀하를 도울 수있는 좋은 예입니다.

내 질문은,이 성격의 일에 대해 단위 테스트를 어떻게 생각 하는가입니다. 없이 IOC 컨테이너?

참고 : 나는 IOC 컨테이너에 반대하지 않으며 곧 그 길을 내려갈 것입니다. 그러나 나는 그들을 사용하지 않는 사람들을위한 대안이 무엇인지 궁금합니다.

도움이 되었습니까?

해결책

필드의 직접적인 인스턴스화를 유지하고 세터를 제공 할 수 없습니까? 이 경우 단위 테스트 중에만 세터를 호출합니다. 이 같은:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository = new SqlQuestionsRepository();

    // Really only called during unit testing...
    public QuestionsController(IQuestionsRepository repository)
    {
        _repository = repository;
    }
}

나는 .NET에 너무 익숙하지 않지만 Java의 부수적으로 이것은 테스트 가능성을 향상시키기 위해 기존 코드를 리팩터링하는 일반적인 방법입니다. 즉, 이미 사용중인 클래스가 있고 기존 기능을 중단하지 않고 코드 커버리지를 개선하기 위해 수정 해야하는 클래스가있는 경우.

우리 팀은 이전에 이것을 해왔으며, 일반적으로 세터의 가시성을 패키지-프라이버시로 설정하고 테스트 클래스의 패키지를 세터를 호출 할 수 있도록 동일하게 유지했습니다.

다른 팁

컨트롤러가있는 기본 생성자가있는 기본 동작이있을 수 있습니다.

... 같은 ...

public QuestionsController()
    : this(new QuestionsRepository())
{
}

이렇게하면 기본적으로 컨트롤러 팩토리가 컨트롤러의 새 인스턴스를 생성 할 때 기본 생성자의 동작을 사용합니다. 그런 다음 장치 테스트에서 조롱 프레임 워크를 사용하여 다른 생성자로 조롱을 전달할 수 있습니다.

한 가지 옵션은 가짜를 사용하는 것입니다.

public class FakeQuestionsRepository : IQuestionsRepository {
    public FakeQuestionsRepository() { } //simple constructor
    //implement the interface, without going to the database
}

[TestFixture] public class QuestionsControllerTest {
    [Test] public void should_be_able_to_instantiate_the_controller() {
        //setup the scenario
        var repository = new FakeQuestionsRepository();
        var controller = new QuestionsController(repository);
        //assert some things on the controller
    }
}

또 다른 옵션은 조롱과 조롱 프레임 워크를 사용하는 것입니다.이 프레임 워크.

[TestFixture] public class QuestionsControllerTest {
    [Test] public void should_be_able_to_instantiate_the_controller() {
        //setup the scenario
        var repositoryMock = new Moq.Mock<IQuestionsRepository>();
        repositoryMock
            .SetupGet(o => o.FirstQuestion)
            .Returns(new Question { X = 10 });
        //repositoryMock.Object is of type IQuestionsRepository:
        var controller = new QuestionsController(repositoryMock.Object);
        //assert some things on the controller
    }
}

모든 객체가 구성되는 위치와 관련하여. 단위 테스트에서는 테스트중인 실제 객체와 테스트중인 실제 객체가 요구하는 가짜 또는 조롱 된 종속성 인 최소한의 객체 세트 만 설정합니다. 예를 들어, 테스트중인 실제 객체는 인스턴스입니다. QuestionsController - 의존성이 있습니다 IQuestionsRepository, 그래서 우리는 그것을 가짜로 준다 IQuestionsRepository 첫 번째 예 또는 모의처럼 IQuestionsRepository 두 번째 예에서와 마찬가지로.

그러나 실제 시스템에서는 소프트웨어의 최상위 레벨에 컨테이너 전체를 설정합니다. 예를 들어 웹 응용 프로그램에서 컨테이너를 설정하고 모든 인터페이스와 구현 클래스를 배선합니다. GlobalApplication.Application_Start.

나는 베드로의 대답을 조금 확장하고 있습니다.

많은 엔티티 유형이있는 응용 프로그램에서 컨트롤러가 여러 리포지토리, 서비스 등에 대한 참조를 요구하는 것은 드문 일이 아닙니다. 테스트 코드에서 모든 종속성을 수동으로 전달하는 것이 지루합니다 (특히 주어진 테스트에는 한두 가지 중 하나만 포함 할 수 있기 때문입니다). 이러한 시나리오에서는 생성자 주입보다 Setter Injection 스타일 IOC를 선호합니다. 내가 사용하는 패턴 :

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository Repository 
    {
        get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); }
        set { _repo = value; }
    }
    private IQuestionsRepository _repo;

    // Don't need anything fancy in the ctor
    public QuestionsController()
    {
    }
}

바꾸다 IoC.GetInstance<> 특정 IOC 프레임 워크가 사용하는 모든 구문으로.

생산 사용에서는 아무것도 속성 세터를 호출하지 않으므로 getter가 처음으로 컨트롤러라고 불리는 경우 IOC 프레임 워크를 호출하고 인스턴스를 가져 와서 저장합니다.

테스트에서 컨트롤러 메소드를 호출하기 전에 세터를 호출하면됩니다.

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
}

이 접근법의 이점, IMHO :

  1. 여전히 생산에서 IOC를 활용합니다.
  2. 테스트 중에 컨트롤러를 수동으로 구성하기가 더 쉽습니다. 테스트가 실제로 사용하는 종속성을 초기화하면됩니다.
  3. 공통 종속성을 수동으로 구성하지 않으려면 테스트 별 IOC 구성을 생성 할 수 있습니다.
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top