2012-03-25 4 views
2

Inversion of Control을 이해하려고 노력하고 있으며, 단위 테스트에 어떻게 도움이되는지 알고 싶습니다. 필자는 IOC에 대한 여러 온라인 설명을 읽었으며 그 내용을 이해했지만 아직 이해하지 못했습니다.Inversion of Control은 어떻게 도움이됩니까?

단위 테스트를 위해 StructureMap을 사용하는 샘플 프로젝트를 개발했습니다. 다음과 같은 StructureMap 설정 코드 : 내가보기로

private readonly IAccountRepository _accountRepository 

public Logon() 
{ 
    _accountRepository = ObjectFactory.GetInstance<IAccountRepository>(); 
} 

그래도 이해하지 못하는 해요 것은, 단순히 다음과 같은 위를 선언 할 수있다 :

AccountRepository _accountRepository = new AccountRepository(); 

그리고 그것은 할 것 이전 코드와 동일합니다. 그래서, 누군가가 간단한 방식으로 설명 할 수 있는지, IOC 사용의 이점 (특히 단위 테스트를 다룰 때)이 궁금합니다.

감사

+1

그건 IoC가 아닙니다. –

답변

2

Inversion of Control은 프레임 워크 호출을 사용자 코드로 되돌리기위한 개념입니다. . 이는 매우 추상적 인 개념이며 단순히 라이브러리와 프레임 워크의 차이점을 설명하거나 "프레임 워크의 특성 정의"로 설명 될 수 있습니다. 라이브러리는 코드에 의해 호출되는 반면 프레임 워크는 코드를 제어합니다. 모든 프레임 워크는 코드를 플러그인 할 수있는 후크를 제공합니다.

제어 반전은 프레임 워크를 만드는 동안 프레임 워크 개발자이거나 프레임 워크 코드와 상호 작용하는 응용 프로그램 개발자 인 경우에만 적용 할 수있는 패턴입니다. 그러나 IoC는 응용 프로그램 코드 만 사용하는 경우에는 적용되지 않습니다.

구현 대신 추상화에 의존하는 동작을 종속성 반전이라고하며, 종속성 반전은 응용 프로그램 및 프레임 워크 개발자가 모두 수행 할 수 있습니다. 그래서 당신이 IoC로 부르는 것은 실제로 Dependency Inversion입니다. Krzysztof가 이미 언급했듯이 : 당신이하는 일은 IoC가 아닙니다. Dependency Inversion에 대해서는 앞으로 논의 할 것입니다.

기본적으로 Dependency Inversion의 두 가지 형식/구현이 있습니다. Service Locator와 Dependency Injection이 있습니다.

서비스 검색 자 패턴을 사용하면 종속성이 필요한 클래스 내에서 정적 팩토리를 호출합니다. 일반적으로, 다음과 같습니다 당신은 서비스 로케이터 패턴을 사용하고 있습니다 :

public class Service 
{ 
    public void SomeOperation() { 
     IDependency dependency = 
      ServiceLocator.GetInstance<IDependency>(); 
     dependency.Execute(); 
    } 
} 

이 예제는 당신이 당신의 Logon 방법에하고있는 일이 있기 때문에, 당신에게 익숙 할 것이다.

종속성 삽입 패턴을 사용하면 외부에서 필요로하는 모든 종속성을 주입 할 수 있습니다. 바람직하게는 생성자를 사용한다. 클래스 자체는 의존성을 가져올 책임이 없습니다. 그 책임은 호출 스택 위로 이동됩니다. 이전 클래스는 사용 종속성 주입과 같습니다

public class Service 
{ 
    private readonly IDependency dependency; 

    public Service(IDependency dependency) 

{ this.dependency = 의존성; }

public void SomeOperation() 

{ this.dependency.Execute(); 두 경우 모두에서 Service 클래스는 종속성을 만드는 책임을지지 않습니다이며 사용되는 구현을 알 수 없기 때문에 } }

두 패턴 종속성 반전이다. 인터페이스와 만 통신합니다. 두 패턴 모두 클래스가 사용하는 구현에 대해 유연성을 제공하므로보다 유연한 소프트웨어를 작성할 수 있습니다.

그러나 서비스 로케이터 패턴에는 많은 문제점이 있으며, 이것이 안티 패턴으로 간주되는 이유입니다. Dependency Inversion (귀하의 경우 Service Locator)이 단위 테스트에 어떻게 도움이되는지 궁금해하기 때문에 이미 이러한 문제가 발생하고 있습니다.

대답은 Service Locator 패턴이 단위 테스트에 도움이되지 않는다는 것입니다. 오히려 단위 테스트가 매우 어렵습니다. 클래스가 ObjectFactory을 호출하게함으로써 두 클래스 사이에 강력한 종속성을 생성하게됩니다. 테스트를 위해 IAccountRepository을 대체하면 단위 테스트에 ObjectFactory을 사용해야 함을 의미합니다. 이렇게하면 단위 테스트를 읽기가 더 어려워집니다. 그러나 더 중요한 것은 ObjectFactory이 정적 인스턴스이기 때문에 모든 유닛 테스트가 동일한 인스턴스를 사용하므로 테스트별로 격리 및 스왑 구현에서 테스트를 실행하기가 어렵습니다.

이전에는 Service Locator 패턴을 사용 했었고,이를 처리하는 방법은 서비스 로케이터에 스레드 기반으로 변경할 수있는 종속성을 등록하는 것입니다 ([ThreadStatic] 필드 사용). 표지). 이를 통해 테스트를 격리 된 상태로 유지하면서 내 테스트를 병렬로 실행할 수있었습니다 (기본적으로 MSTest가 수행하는 작업). 그러나이 문제는이 작업이 매우 복잡해지고 모든 종류의 기술 자료로 테스트가 복잡해졌으며 더 많은 테스트를 작성하면서 많은 시간을 소비하여 이러한 기술적 인 문제를 해결할 수있었습니다.

이러한 문제에 대한 진정한 해결책은 종속성 주입입니다. 클래스가 생성자를 통해 필요로하는 종속성을 주입하면 모든 문제가 사라집니다. 이렇게하면 클래스가 필요로하는 종속성 (숨겨진 종속성이 없음)이 무엇인지 명확하게 알 수있을뿐만 아니라 모든 유닛 테스트 자체가 필요한 종속성을 주입하는 역할을합니다. 따라서 필기 테스트가 훨씬 쉬워지고 단위 테스트에서 DI 컨테이너를 구성하지 않아도됩니다.

추가 읽기 : Service Locator is an Anti-Pattern.

+0

의존성을 해결하기위한 프레임 워크가 필요 없기 때문에 생성자를 통해 종속성을 전달하는 것이 더 쉽다는 점에 동의합니다. 그러나 "정적 인스 턴 스"인수에 동의하지 않습니다. Singelton으로 구현할 필요가 없습니다. 이것은 기본적으로 일부 데이터 액세스 객체의 인스턴스를 반환하는 정적 메서드입니다. 공장을 통해 공유 상태가 없기 때문에 테스트를 병렬로 실행하려고 할 때 문제가 발생하지 않습니다. – TGH

+2

제어 반전의 정의는 컨테이너가 구성 요소를 선택하고 구성 요소를 선택하는 것이 아닙니다. IoC의 핵심은 느슨하게 결합되고 더 쉽게 테스트 할 수있는 구성 요소를 만드는 것입니다. Service Locator 패턴을 사용하면 구성 요소 클래스가 컨테이너를 선택/사용하지만 컨테이너가 반환 할 IDependency를 선택합니다. 따라서 Service Locator 패턴은 중간에 있습니다. 당신은 하나의 강건한 의존을 다른 것으로 바꾸어야합니다. –

1

이 뒤에 아이디어는보다 단위 테스트 가능한 버전의 기본 계정 저장소 구현을 교환 할 수 있도록하는 것입니다. 유닛 테스트에서 데이터베이스 호출을하지 않고 대신 고정 데이터를 반환하는 버전을 인스턴스화 할 수 있습니다. 이렇게하면 메서드의 논리를 테스트하는 데 집중할 수 있고 데이터베이스에 대한 종속성을 없앨 수 있습니다.

이 많은 수준에 더 나은 : 당신은 더 이상 전화하지 않기 때문에 빠른 데이터베이스 2) 테스트 실행에서 데이터 변경으로 인해 실패한 테스트에 대해 걱정할 필요가 없기 때문에 1) 귀하의 테스트가 더 안정적이다 외부 데이터 소스에 연결 3) 조롱 된 저장소가 모든 조건을 테스트하는 데 필요한 모든 유형의 데이터를 반환 할 수 있기 때문에보다 쉽게 ​​모든 테스트 조건을 시뮬레이션 할 수 있습니다.

0

질문에 답하는 열쇠는 테스트 가능성과 주입 된 개체의 수명을 관리하려는 경우 또는 IoC 컨테이너에서 수행하도록 허용하려는 경우입니다.

예를 들어 저장소를 사용하는 클래스를 작성하고 테스트하려고한다고 가정 해 보겠습니다.

다음과 같은 일을 할 경우 1. 이미 설정 데이터베이스가 있어야합니다 :이 방법을 테스트 할 때

public class MyClass 
{ 
    public MyEntity GetEntityBy(long id) 
    { 
     AccountRepository _accountRepository = new AccountRepository(); 

     return _accountRepository.GetEntityFromDatabaseBy(id); 
    } 
} 

당신은 합병증이 많이 있다는 것을 발견 할 것이다. 2. 데이터베이스에 찾고있는 엔티티가있는 테이블이 있어야합니다. 3. 테스트에 사용중인 ID가 있어야합니다. 이유가 무엇이든 삭제하면 자동 테스트가 중단됩니다.

대신 다음과 같은 경우 :

public interface IAccountRepository 
{ 
    AccountEntity GetAccountFromDatabase(long id); 
} 

public class AccountRepository : IAccountRepository 
{ 
    public AccountEntity GetAccountFromDatabase(long id) 
    { 
     //... some DB implementation here 
    } 
} 

public class MyClass 
{ 
    private readonly IAccountRepository _accountRepository; 

    public MyClass(IAccountRepository accountRepository) 
    { 
     _accountRepository = accountRepository; 
    } 

    public AccountEntity GetAccountEntityBy(long id) 
    { 
     return _accountRepository.GetAccountFromDatabase(id) 
    } 
} 

이제 당신은 당신이 데이터베이스가 장소에 할 필요없이 격리에서 MyClass에 클래스를 테스트 할 수 있다고합니다.

이것은 어떤 이점이 있습니까? 예를 들어 당신이 이런 일을 할 수 (Visual Studio를 사용하는 가정,하지만 같은 원칙은, 예를 들면 NUnit과 적용) :

[TestClass] 
public class MyClassTests 
{ 
    [TestMethod] 
    public void ShouldCallAccountRepositoryToGetAccount() 
    { 
     FakeRepository fakeRepository = new FakeRepository(); 

     MyClass myClass = new MyClass(fakeRepository); 

     long anyId = 1234; 

     Account account = myClass.GetAccountEntityBy(anyId); 

     Assert.IsTrue(fakeRepository.GetAccountFromDatabaseWasCalled); 
     Assert.IsNotNull(account); 
    } 
} 

public class FakeRepository : IAccountRepository 
{ 
    public bool GetAccountFromDatabaseWasCalled { get; private set; } 

    public Account GetAccountFromDatabase(long id) 
    { 
     GetAccountFromDatabaseWasCalled = true; 

     return new Account(); 
    } 
} 

을 그래서, 테스트 할, 당신은 아주 자신있게 할 수 있습니다 볼 수 MyClass 클래스는 IAccountRepository 인스턴스를 사용하여 데이터베이스를 제자리에 둘 필요없이 데이터베이스에서 Account 엔터티를 가져옵니다.

예제를 개선하기 위해 여기서도 할 수있는 일이 많이 있습니다. Rhino Mock 또는 Moq와 같은 Mocking 프레임 워크를 사용하여 예제에서와 같이 직접 코딩하는 대신 위조 된 객체를 만들 수 있습니다.

이렇게하면 MyClass 클래스가 AccountRepository와 완전히 독립적이므로 Loosley 결합 개념이 작동하고 응용 프로그램을 테스트하고 유지 관리 할 수 ​​있습니다.

이 예제에서는 IoC의 장점을 직접 볼 수 있습니다. 이제 IoC 컨테이너를 사용하지 않으면 모든 종속성을 인스턴스화하고 Composition Root에 적절하게 주입하거나 IoC 컨테이너를 구성하여 사용자를 대신 할 수 있도록해야합니다.

감사합니다.

관련 문제