[c#] 프로그래머가 “객체가 아닌 인터페이스에 대한 코드”라는 말은 무엇을 의미합니까?

저는 TDD를 배우고 내 워크 플로우에 적용 하기 위해 매우 길고 힘든 탐구를 시작했습니다 . 나는 TDD가 IoC 원칙에 매우 잘 맞는다는 인상을 받고 있습니다.

여기에 TDD 태그가 달린 질문 중 일부를 살펴본 후 객체가 아닌 인터페이스에 대해 프로그래밍하는 것이 좋습니다.

이것이 무엇인지에 대한 간단한 코드 예제를 제공하고 실제 사용 사례에 적용하는 방법을 제공 할 수 있습니까? 간단한 예는 저 (및 배우고 싶은 다른 사람들)가 개념을 이해하는 데 중요합니다.



답변

중히 여기다:

class MyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(MyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}

MyMethod만 허용 하기 때문에 단위 테스트를 위해 모의 객체 MyClass로 바꾸려면 MyClass할 수 없습니다. 인터페이스를 사용하는 것이 더 좋습니다.

interface IMyClass
{
    void Foo();
}

class MyClass : IMyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(IMyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}

이제 MyMethod특정 구체적인 구현이 아닌 인터페이스 만 사용하므로을 테스트 할 수 있습니다 . 그런 다음 해당 인터페이스를 구현하여 테스트 목적으로 원하는 모의 또는 가짜를 만들 수 있습니다. Rhino Mocks ‘와 같은 라이브러리도 있습니다.이 라이브러리 Rhino.Mocks.MockRepository.StrictMock<T>()는 모든 인터페이스를 사용하여 즉시 모의 개체를 만듭니다.


답변

그것은 모두 친밀감의 문제입니다. 구현 (실현 된 객체)에 코딩하는 경우 해당 “다른”코드와 그 소비자로서 매우 밀접한 관계에 있습니다. 그것은 당신이 그것을 구성하는 방법 (즉, 생성자 매개 변수로서, 아마도 setter로서 어떤 의존성을 가지고 있는지), 언제 폐기해야하는지 알아야하며, 아마도 그것 없이는 많은 일을 할 수 없다는 것을 의미합니다.

실현 된 객체 앞의 인터페이스를 통해 몇 가지 작업을 수행 할 수 있습니다.

  1. 하나의 경우 팩토리를 활용하여 객체의 인스턴스를 구성 할 수 있습니다. IOC 컨테이너는이를 매우 잘 수행하거나 직접 만들 수 있습니다. 책임을 벗어난 건설 의무를 사용하면 코드가 필요한 것을 얻고 있다고 가정 할 수 있습니다. 공장 벽의 다른 쪽에서는 실제 인스턴스를 만들거나 클래스의 모의 인스턴스를 만들 수 있습니다. 프로덕션에서는 물론 real을 사용하지만 테스트를 위해 시스템을 실행하지 않고도 다양한 시스템 상태를 테스트하기 위해 스텁 또는 동적 모의 인스턴스를 만들 수 있습니다.
  2. 물체가 어디에 있는지 알 필요가 없습니다. 이것은 대화하려는 개체가 프로세스 또는 시스템에 로컬 일 수도 있고 아닐 수도있는 분산 시스템에서 유용합니다. Java RMI 또는 이전 skool EJB를 프로그래밍 한 적이 있다면 클라이언트가 신경 쓸 필요가없는 원격 네트워킹 및 마샬링 임무를 수행하는 프록시를 숨기는 “인터페이스와 대화”하는 루틴을 알고 있습니다. WCF는 “인터페이스와 대화”라는 유사한 철학을 가지고 있으며 시스템이 대상 개체 / 서비스와 통신하는 방법을 결정하도록합니다.

** 업데이트 ** IOC 컨테이너 (공장)의 예에 대한 요청이있었습니다. 거의 모든 플랫폼에 대해 많은 것이 있지만 핵심에서는 다음과 같이 작동합니다.

  1. 애플리케이션 시작 루틴에서 컨테이너를 초기화합니다. 일부 프레임 워크는 구성 파일이나 코드 또는 둘 다를 통해이를 수행합니다.

  2. 컨테이너가 구현하는 인터페이스에 대한 팩토리로 생성 할 구현을 “등록”합니다 (예 : 서비스 인터페이스에 MyServiceImpl 등록). 이 등록 프로세스 동안 일반적으로 제공 할 수있는 몇 가지 동작 정책이 있습니다 (예 : 매번 새 인스턴스가 생성되거나 단일 인스턴스가 사용되는 경우)

  3. 컨테이너가 객체를 생성 할 때 생성 프로세스의 일부로 해당 객체에 종속성을 주입합니다 (즉, 객체가 다른 인터페이스에 의존하는 경우 해당 인터페이스의 구현이 차례로 제공됩니다).

의사 코드처럼 다음과 같이 보일 수 있습니다.

IocContainer container = new IocContainer();

//Register my impl for the Service Interface, with a Singleton policy
container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON);

//Use the container as a factory
Service myService = container.Resolve<Service>();

//Blissfully unaware of the implementation, call the service method.
myService.DoGoodWork();


답변

인터페이스에 대해 프로그래밍 할 때 구체적인 유형이 아닌 인터페이스의 인스턴스를 사용하는 코드를 작성합니다. 예를 들어 생성자 주입을 통합하는 다음 패턴을 사용할 수 있습니다. 생성자 주입 및 제어 반전의 다른 부분은 인터페이스에 대해 프로그래밍 할 필요가 없지만 TDD 및 IoC 관점에서 왔기 때문에 이러한 방식으로 연결하여 일부 컨텍스트를 제공 할 수 있습니다. 익숙합니다.

public class PersonService
{
    private readonly IPersonRepository repository;

    public PersonService(IPersonRepository repository)
    {
        this.repository = repository;
    }

    public IList<Person> PeopleOverEighteen
    {
        get
        {
            return (from e in repository.Entities where e.Age > 18 select e).ToList();
        }
    }
}

저장소 개체가 전달되고 인터페이스 유형입니다. 인터페이스 전달의 이점은 사용법을 변경하지 않고 구체적인 구현을 ‘교환’할 수 있다는 것입니다.

예를 들어 런타임에 IoC 컨테이너가 데이터베이스에 연결되는 저장소를 주입한다고 가정합니다. 테스트 시간 동안 모의 또는 스텁 저장소를 전달하여 PeopleOverEighteen방법 을 실행할 수 있습니다 .


답변

일반적인 생각을 의미합니다. 구체적이지 않습니다.

사용자에게 메시지를 보내는 것을 알리는 애플리케이션이 있다고 가정합니다. 예를 들어 IMessage 인터페이스를 사용하여 작업하는 경우

interface IMessage
{
    public void Send();
}

사용자별로 메시지 수신 방식을 사용자 정의 할 수 있습니다. 예를 들어 누군가 이메일로 알림을 받기를 원하므로 IoC가 EmailMessage 구체적인 클래스를 생성합니다. 다른 사람들은 SMS를 원하고 SMSMessage의 인스턴스를 만듭니다.

이 모든 경우에 사용자에게 알리는 코드는 변경되지 않습니다. 다른 구체적인 클래스를 추가하더라도.


답변

단위 테스트를 수행 할 때 인터페이스에 대한 프로그래밍의 큰 장점은 별도로 테스트하거나 테스트 중에 시뮬레이션하려는 종속성에서 코드 조각을 분리 할 수 ​​있다는 것입니다.

앞서 언급 한 예는 구성 값에 액세스하기 위해 인터페이스를 사용하는 것입니다. ConfigurationManager를 직접 보는 대신 구성 값에 액세스 할 수있는 하나 이상의 인터페이스를 제공 할 수 있습니다. 일반적으로 구성 파일에서 읽는 구현을 제공하지만 테스트를 위해 테스트 값을 반환하거나 예외를 던지는 구현을 사용할 수 있습니다.

데이터 액세스 계층도 고려하십시오. 비즈니스 로직이 특정 데이터 액세스 구현과 밀접하게 연결되어 있으면 필요한 데이터를 사용할 수있는 전체 데이터베이스 없이는 테스트하기가 어렵습니다. 데이터 액세스가 인터페이스 뒤에 숨겨져있는 경우 테스트에 필요한 데이터 만 제공 할 수 있습니다.

인터페이스를 사용하면 테스트에 사용할 수있는 “표면 영역”이 증가하여 실제로 코드의 개별 단위를 테스트하는 세밀한 테스트가 가능합니다.


답변

문서를 읽은 후 코드를 사용할 사람처럼 코드를 테스트하십시오. 코드를 작성했거나 읽었으므로 가지고있는 지식을 바탕으로 아무것도 테스트하지 마십시오. 코드 가 예상대로 작동 하는지 확인하려고합니다 .

최상의 경우에는 테스트를 예제로 사용할 수 있어야합니다. Python의 doctest가 이에 대한 좋은 예제입니다.

이러한 지침을 따르면 구현 변경이 문제가되지 않습니다.

또한 내 경험상 애플리케이션의 각 “계층”을 테스트하는 것이 좋습니다. 당신은 그 자체로 의존성이없는 원자 단위를 갖게 될 것이고 결국 그 자체가 단위 인 애플리케이션에 도달 할 때까지 다른 단위에 의존하는 단위를 갖게 될 것입니다.

각 계층을 테스트해야합니다. 단위 A를 테스트하여 단위 A가 의존하는 단위 B도 테스트한다는 사실에 의존하지 마십시오 (규칙은 상속에도 적용됩니다.) 이것 역시 구현 세부 사항으로 취급되어야합니다. 자신을 반복하는 것처럼 느낄 수도 있습니다.

한 번 작성된 테스트는 변경 될 가능성이 없지만 테스트하는 코드는 거의 확실하게 변경됩니다.

실제로는 IO 및 외부 세계의 문제도 있으므로 필요한 경우 모의를 만들 수 있도록 인터페이스를 사용하려고합니다.

좀 더 동적 인 언어에서는별로 문제가되지 않습니다. 여기에서 덕 타이핑, 다중 상속 및 믹스 인을 사용하여 테스트 케이스를 작성할 수 있습니다. 일반적으로 상속을 싫어하기 시작하면 아마도 제대로하고있는 것입니다.


답변

이 스크린 캐스트 는 C #을위한 애자일 개발 및 TDD에 대해 설명합니다.

인터페이스에 대한 코딩은 테스트에서 실제 개체 대신 모의 개체를 사용할 수 있음을 의미합니다. 좋은 모의 프레임 워크를 사용하면 원하는대로 모의 객체에서 할 수 있습니다.