unit-testing
의존성 주입
수색…
비고
소프트웨어 작성으로 취할 수있는 한 가지 접근 방법은 필요에 따라 종속성을 만드는 것입니다. 이것은 프로그램을 작성하는 데 매우 직관적 인 방법이며 대부분 따르는 것이 쉽기 때문에 대부분의 사람들이 가르치는 경향이 있습니다. 이 접근 방식의 문제점 중 하나는 테스트하기가 어려울 수 있다는 것입니다. 현재 날짜를 기반으로 일부 처리를 수행하는 메소드를 고려하십시오. 메서드에 다음과 같은 코드가 포함될 수 있습니다.
if (DateTime.Now.Date > processDate)
{
// Do some processing
}
코드는 현재 날짜에 직접 종속됩니다. 이 방법은 현재 날짜를 쉽게 조작 할 수 없으므로 테스트하기가 어렵습니다. 코드를보다 쉽게 테스트 할 수있는 한 가지 방법은 현재 날짜에 대한 직접 참조를 제거하고 현재 날짜를 처리를 수행하는 메서드에 제공 (또는 주입)하는 것입니다. 이 종속성 삽입은 테스트 double 을 사용하여 단위 테스트의 설정 단계를 단순화함으로써 코드 측면을 훨씬 쉽게 테스트 할 수있게합니다.
IOC 시스템
고려해야 할 또 다른 측면은 종속성의 수명입니다. 클래스 자체가 자체 의존성 (invariants라고도 함)을 생성하는 경우, 클래스 자체는 자체의 종속성을 처리합니다. 의존성 주입 (Dependency Injection)은 이것을 뒤집습니다 (이것이 주입 라이브러리를 종종 "Inversion of Control"시스템으로 지칭하는 이유입니다). 클래스가 의존성을 생성, 관리 및 정리하는 대신에 외부 에이전트 예를 들어, IoC 시스템) 대신 그것을 수행합니다.
따라서 동일한 클래스의 인스턴스간에 공유되는 종속성을 훨씬 간단하게 만들 수 있습니다. 예를 들어 클래스가 소비 할 수 있도록 HTTP 끝점에서 데이터를 가져 오는 서비스를 생각해보십시오. 이 서비스는 무 상태 (즉, 내부 상태가 없음)이므로 애플리케이션 전반에 걸쳐이 서비스의 인스턴스 하나만 있으면됩니다. 이 작업을 수동으로 수행하는 것은 가능하지만 (예 : 정적 클래스를 사용하여) 클래스를 생성하고 IoC 시스템에 싱글 톤 으로 생성된다는 것을 알리는 것이 훨씬 간단합니다. 따라서 클래스의 인스턴스가 하나씩 만 존재합니다.
또 다른 예로 웹 응용 프로그램에서 사용되는 데이터베이스 컨텍스트가 있습니다. 컨트롤러 또는 인스턴스별로 요청 (또는 스레드)마다 새 컨텍스트가 필요합니다. 이렇게하면 수동으로 전달하지 않고 해당 스레드가 실행하는 모든 계층에 컨텍스트를 주입 할 수 있습니다.
이렇게하면 소비 클래스가 종속성을 관리하지 않아도됩니다.
생성자 삽입
생성자 삽입은 전체 클래스가 의존하는 종속성을 주입하는 가장 안전한 방법입니다. 이러한 종속성은 종종 클래스의 인스턴스를 제공하지 않으면 생성 할 수 없으므로 불변성 (invariant) 이라고합니다. 구성시 종속성을 주입하도록 요구함으로써 객체가 일관성없는 상태로 생성 될 수 없다는 것을 보장합니다.
오류 조건에서 로그 파일에 작성해야하는 클래스를 고려하십시오. ILogger
는 필요에 따라 주입 및 사용할 수있는 ILogger
에 의존합니다.
public class RecordProcessor
{
readonly private ILogger _logger;
public RecordProcessor(ILogger logger)
{
_logger = logger;
}
public void DoSomeProcessing() {
// ...
_logger.Log("Complete");
}
}
때로는 테스트를 작성하는 동안 생성자가 테스트되는 경우에 실제로 필요한 것보다 더 많은 종속성이 필요하다는 것을 알 수 있습니다. 더 많은 테스트가있을수록 학급에서 Single Responsibility Principle (SRP)을 위반할 확률이 높아집니다. 그래서 잠재적 인 경고 신호를 숨길 수 있으므로 테스트 클래스 초기화 단계에서 주입 된 종속성의 모든 동작에 대해 기본 동작을 정의하는 것은 그리 좋은 방법이 아닙니다.
unittest는 다음과 같습니다.
[Test]
public void RecordProcessor_DependencyInjectionExample()
{
ILogger logger = new FakeLoggerImpl(); //or create a mock by a mocking Framework
var sut = new RecordProcessor(logger); //initialize with fake impl in testcode
Assert.IsTrue(logger.HasCalledExpectedMethod());
}
속성 주입
속성 삽입은 클래스 의존성이 생성 된 후에 업데이트되도록합니다. 이 기능은 객체 생성을 단순화하면서도 테스트 더블을 사용하여 테스트에서 종속성을 무시할 수있는 경우 유용 할 수 있습니다.
오류 조건에서 로그 파일에 작성해야하는 클래스를 고려하십시오. 클래스는 기본 Logger
를 구성하는 방법을 알고 있지만 속성 삽입을 통해 재정의 할 수 있습니다. 그러나이 예제에서는 속성 주입을 사용하여이 클래스를 ConcreteLogger
인 ILogger
의 정확한 구현과 밀접하게 연결한다는 점에 ILogger
필요가 있습니다. 가능한 해결 방법은 필요한 ILogger 구현을 반환하는 팩토리 일 수 있습니다.
public class RecordProcessor
{
public RecordProcessor()
{
Logger = new ConcreteLogger();
}
public ILogger Logger { get; set; }
public void DoSomeProcessing()
{
// ...
_logger.Log("Complete");
}
}
대부분의 경우 Constructor Injection은 Property Injection보다 바람직합니다. Constructor Injection은 생성 직후 오브젝트의 상태에 대한 더 나은 보장을 제공하기 때문입니다.
방법 주입
메소드 삽입은 종속성을 처리에 주입하는 정교한 방법입니다. 현재 날짜를 기반으로 일부 처리를 수행하는 메소드를 고려하십시오. 현재 날짜는 테스트에서 변경하기가 어렵 기 때문에 테스트하려는 메서드에 날짜를 전달하는 것이 훨씬 쉽습니다.
public void ProcessRecords(DateTime currentDate)
{
foreach(var record in _records)
{
if (currentDate.Date > record.ProcessDate)
{
// Do some processing
}
}
}
컨테이너 / DI 프레임 워크
코드에서 종속성을 추출하여 삽입 할 수있게하면 코드를 테스트하기가 쉬워 지므로 문제가 계층 구조 위로 올라 가게되고 결과적으로 생성하기 어려운 개체가 될 수도 있습니다. 다양한 의존성 주입 프레임 워크 / Inversion of Control Container가이 문제를 해결할 수 있도록 작성되었습니다. 이것들은 타입 맵핑이 등록되도록합니다. 이러한 등록은 컨테이너가 오브젝트를 생성하도록 요청 될 때 종속성을 해결하는 데 사용됩니다.
다음 클래스를 고려하십시오.
public interface ILogger {
void Log(string message);
}
public class ConcreteLogger : ILogger
{
public ConcreteLogger()
{
// ...
}
public void Log(string message)
{
// ...
}
}
public class SimpleClass
{
public SimpleClass()
{
// ...
}
}
public class SomeProcessor
{
public SomeProcessor(ILogger logger, SimpleClass simpleClass)
{
// ...
}
}
SomeProcessor
를 구성하기 위해서는 ILogger
와 SimpleClass
의 인스턴스가 필요합니다. Unity와 같은 컨테이너는이 프로세스를 자동화하는 데 도움이됩니다.
먼저 컨테이너를 구성한 다음 매핑을 등록해야합니다. 이는 대개 응용 프로그램 내에서 한 번만 수행됩니다. 이것이 발생하는 시스템 영역은 일반적으로 Composition Root
// Register the container
var container = new UnityContainer();
// Register a type mapping. This allows a `SimpleClass` instance
// to be constructed whenever it is required.
container.RegisterType<SimpleClass, SimpleClass>();
// Register an instance. This will use this instance of `ConcreteLogger`
// Whenever an `ILogger` is required.
container.RegisterInstance<ILogger>(new ConcreteLogger());
컨테이너를 구성한 후에는 필요에 따라 종속성을 자동으로 해결하여 객체를 만드는 데 사용할 수 있습니다.
var processor = container.Resolve<SomeProcessor>();