프로그래머가 "객체가 아닌 인터페이스에 대한 코드"라는 말은 무엇을 의미합니까?
저는 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로서 어떤 의존성을 가지고 있는지), 언제 폐기해야하는지 알아야하며, 아마도 그것 없이는 많은 일을 할 수 없다는 것을 의미합니다.
실현 된 객체 앞의 인터페이스를 통해 몇 가지 작업을 수행 할 수 있습니다.
- 하나의 경우 팩토리를 활용하여 객체의 인스턴스를 구성 할 수 있습니다. IOC 컨테이너는이를 매우 잘 수행하거나 직접 만들 수 있습니다. 책임을 벗어난 건설 의무를 사용하면 코드가 필요한 것을 얻고 있다고 가정 할 수 있습니다. 공장 벽의 다른 쪽에서는 실제 인스턴스를 만들거나 클래스의 모의 인스턴스를 만들 수 있습니다. 프로덕션에서는 물론 real을 사용하지만 테스트를 위해 시스템을 실행하지 않고도 다양한 시스템 상태를 테스트하기 위해 스텁 또는 동적으로 모의 인스턴스를 만들 수 있습니다.
- 물체가 어디에 있는지 알 필요가 없습니다. 이것은 대화하려는 개체가 프로세스 또는 시스템에 로컬 일 수도 있고 아닐 수도있는 분산 시스템에서 유용합니다. Java RMI 또는 이전 skool EJB를 프로그래밍 한 적이 있다면 클라이언트가 신경 쓸 필요가없는 원격 네트워킹 및 마샬링 임무를 수행하는 프록시를 숨기는 "인터페이스와 대화"하는 루틴을 알고 있습니다. WCF는 "인터페이스와 대화"라는 유사한 철학을 가지고 있으며 시스템이 대상 개체 / 서비스와 통신하는 방법을 결정하도록합니다.
** 업데이트 ** IOC 컨테이너 (공장)의 예에 대한 요청이있었습니다. 거의 모든 플랫폼에 대해 많은 것이 있지만 핵심에서는 다음과 같이 작동합니다.
애플리케이션 시작 루틴에서 컨테이너를 초기화합니다. 일부 프레임 워크는 구성 파일이나 코드 또는 둘 다를 통해이를 수행합니다.
컨테이너가 구현하는 인터페이스에 대한 팩토리로 생성 할 구현을 "등록"합니다 (예 : 서비스 인터페이스에 MyServiceImpl 등록). 이 등록 프로세스 중에 일반적으로 제공 할 수있는 몇 가지 동작 정책이 있습니다 (예 : 매번 새 인스턴스가 생성되거나 단일 인스턴스가 사용되는 경우)
컨테이너가 객체를 생성 할 때 생성 프로세스의 일부로 해당 객체에 종속성을 주입합니다 (즉, 객체가 다른 인터페이스에 의존하는 경우 해당 인터페이스의 구현이 차례로 제공됩니다).
의사 코드처럼 다음과 같이 보일 수 있습니다.
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();
When programming against an interface you will write code that uses an instance of an interface, not a concrete type. For instance you might use the following pattern, which incorporates constructor injection. Constructor injection and other parts of inversion of control aren't required to be able to program against interfaces, however since you're coming from the TDD and IoC perspective I've wired it up this way to give you some context you're hopefully familiar with.
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();
}
}
}
The repository object is passed in and is an interface type. The benefit of passing in an interface is the ability to 'swap out' the concrete implementation without changing the usage.
For instance one would assume that at runtime the IoC container will inject a repository that is wired to hit the database. During testing time, you can pass in a mock or stub repository to exercise your PeopleOverEighteen
method.
It means think generic. Not specific.
Suppose you have an application that notify the user sending him some message. If you work using an interface IMessage for example
interface IMessage
{
public void Send();
}
you can customize, per user, the way they receive the message. For example somebody want to be notified wih an Email and so your IoC will create an EmailMessage concrete class. Some other wants SMS, and you create an instance of SMSMessage.
In all these case the code for notifying the user will never be changed. Even if you add another concrete class.
The big advantage of programming against interfaces when performing unit testing is that it allows you to isolate a piece of code from any dependencies you want to test separately or simulate during the testing.
An example I've mentioned here before somewhere is the use of an interface to access configuration values. Rather than looking directly at ConfigurationManager you can provide one or more interfaces that let you access config values. Normally you would supply an implementation that reads from the config file but for testing you can use one that just returns test values or throws exceptions or whatever.
Consider also your data access layer. Having your business logic tightly coupled to a particular data access implementation makes it hard to test without having a whole database handy with the data you need. If your data access is hidden behind interfaces you can supply just the data you need for the test.
Using interfaces increases the "surface area" available for testing allowing for finer grained tests that really do test individual units of your code.
Test your code like someone who would use it after reading the documentation. Do not test anything based on knowledge you have because you have written or read the code. You want to make sure that your code behaves as expected.
In the best case you should be able to use your tests as examples, doctests in Python are a good example for this.
If you follow these guidelines changing the implementation shouldn't be an issue.
Also in my experience it is good practice to test each "layer" of your application. You will have atomic units, which in itself have no dependencies and you will have units which depend on other units until you eventually get to the application which in itself is a unit.
You should test each layer, do not rely on the fact that by testing unit A you also test unit B which unit A depends on (the rule applies to inheritance as well.) This, too, should be treated as an implementation detail, even though you might feel as if you are repeating yourself.
Keep in mind that once written tests are unlikely to change while the code they test will change almost definitely.
In practice there is also the problem of IO and the outside world, so you want to use interfaces so that you can create mocks if necessary.
In more dynamic languages this is not that much of an issue, here you can use duck typing, multiple inheritance and mixins to compose test cases. If you start disliking inheritance in general you are probably doing it right.
This screencast explains agile development and TDD in practice for c#.
By coding against an interface means that in your test, you can use a mock object instead of the real object. By using a good mock framework, you can do in your mock object whatever you like.
'Program Tip' 카테고리의 다른 글
SQL Server 2008 빈 문자열 대 공백 (0) | 2020.10.12 |
---|---|
TimeZoneInfo를 사용하여 일광 절약 시간 동안 현지 시간을 얻는 방법은 무엇입니까? (0) | 2020.10.12 |
32 비트 및 64 비트 용으로 컴파일 할 때 큰 성능 차이 (26 배 더 빠름) (0) | 2020.10.12 |
Swift @escaping 및 완료 처리기 (0) | 2020.10.12 |
실패 / 오류시 JSON 서비스가 반환해야하는 사항 (0) | 2020.10.12 |