의존성 주입에 대한 점진적 접근


12

의존성 주입을 사용하여 클래스를 단위로 테스트 할 수 있도록 노력하고 있습니다. 그러나이 클래스 중 일부에는 많은 클라이언트가 있으며 종속성을 전달하기 위해 모든 클래스를 리팩터링 할 준비가되지 않았습니다. 그래서 나는 점차적으로 노력하고 있습니다. 기본 종속성을 유지하면서 테스트를 위해 재정의 할 수 있도록합니다.

내가 고민하고있는 한 가지 접근 방식은 모든 "새로운"호출을 고유 한 방법으로 옮기는 것입니다. 예 :

public MyObject createMyObject(args) {
  return new MyObject(args);
}

그런 다음 단위 테스트 에서이 클래스를 하위 클래스로 만들고 create 함수를 재정 의하여 가짜 객체를 대신 만들 수 있습니다.

이것이 좋은 접근입니까? 단점이 있습니까?

보다 일반적으로 테스트를 위해 대체 할 수있는 한 하드 코딩 된 종속성을 갖는 것이 괜찮습니까? 선호하는 접근법은 생성자에서 명시 적으로 요구하는 것임을 알고 있으며 결국 거기에 가고 싶습니다. 그러나 이것이 좋은 첫 단계인지 궁금합니다.

나에게 일어난 한 가지 단점 : 테스트해야 할 실제 서브 클래스가있는 경우 부모 클래스에 대해 작성한 테스트 서브 클래스를 재사용 할 수 없습니다. 각 실제 서브 클래스에 대해 테스트 서브 클래스를 작성해야하며 동일한 작성 함수를 대체해야합니다.


왜 그들이 단위 테스트를 할 수 없는지 자세히 설명 할 수 있습니까?
Austin Salonen

코드 전체에 흩어져있는 많은 "새로운 X"와 정적 클래스 및 싱글 톤이 많이 사용됩니다. 한 클래스를 테스트하려고 할 때 실제로 전체 클래스를 테스트하고 있습니다. 종속성을 격리하고 가짜 개체로 바꿀 수 있으면 테스트를 더 잘 제어 할 수 있습니다.
JW01

답변:


13

이것은 시작하기에 좋은 방법입니다. 참고 가장 중요한 단위 테스트와 기존의 코드를 포함하는 것입니다; 테스트가 완료되면 더 자유롭게 리팩토링 하여 디자인을 더욱 향상시킬 수 있습니다.

따라서 첫 번째 요점은 디자인을 우아하고 반짝이는 것이 아니라 코드 장치를 최소한의 위험 변경으로 테스트수 있도록하는 것 입니다. 단위 테스트가 없으면 코드를 위반하지 않도록 보수적이고 신중해야합니다. 이러한 초기 변경으로 인해 코드가 더 어색하거나 어색하게 보일 수도 있습니다. 그러나 첫 번째 단위 테스트를 작성할 수있게되면 결국 이상적인 설계를 위해 리팩토링 할 수 있습니다.

이 주제에 관해 읽어야 할 기본 작업은 레거시 코드로 효과적으로 작업하는 것 입니다. 그것은 당신이 위에 보여준 트릭과 여러 언어를 사용하여 더 많은 것을 설명합니다.


"테스트가 완료되면 더 자유롭게 리팩토링 할 수 있습니다"라는 말만 남으십시오. 테스트가없는 경우 리팩토링하지 않고 코드 만 변경하는 것입니다.
ocodo

@Slomojo 요점을 알지만 테스트없이 많은 안전한 리팩토링, 특히 (Java의 경우 Eclipse에서) 중복 코드를 메소드로 추출, 모든 이름 바꾸기, 클래스 이동 및 필드 추출, 상수와 같은 자동화 된 IDE ractoring 등
장백의

나는 단지 리팩토링 (Refactoring)이라는 용어의 기원을 인용하고 있는데, 이는 테스트 프레임 워크에 의해 회귀 오류로부터 보호되는 개선 된 패턴으로 코드를 수정하는 것이다. 물론, IDE 기반 리팩토링은 소프트웨어를 '리팩터링'하는 것보다 훨씬 사소한 것이었지만, 소프트웨어에 테스트가없는 경우 자체 망상을 피하는 것을 선호하는 경향이 있으며 코드를 "리팩터링"하고 있습니다. 물론, 그것은 내가 다른 사람에게 말하는 것이 아닐 수도 있습니다;)
ocodo

1
@Alb, 테스트없는 리팩토링은 항상 더 위험합니다. 자동 리팩토링은 위험을 줄이지 만 0은 아닙니다. 도구가 올바르게 처리하지 못하는 까다로운 엣지 케이스가 항상 있습니다. 더 좋은 경우 결과는 도구의 일부 오류 메시지이지만 최악의 경우 자동으로 미묘한 버그가 발생할 수 있습니다. 따라서 자동화 된 도구를 사용해도 가장 간단하고 안전한 변경 사항을 유지하는 것이 좋습니다.
Péter Török

3

Guice 사람들은

@Inject
public void setX(.... X x) {
   this.x = x;
}

정의에 @Inject를 추가하는 대신 모든 속성에 대해 이를 통해 일반 Bean으로 처리 할 수 new있으며 프로덕션에서 테스트 (및 수동으로 특성 설정) 및 @Inject를 수행 할 수 있습니다 .


3

이 유형의 문제에 직면하면 테스트 할 클래스마다의 각 종속성을 식별하고이를 전달할 수있는 보호 된 생성자를 작성합니다. 이는 각각에 대한 세터를 작성하는 것과 사실상 동일하지만 선호합니다. 미래에 인스턴스화하는 것을 잊지 않도록 내 생성자에 종속성을 선언합니다.

따라서 새로운 단위 테스트 가능 클래스에는 2 개의 생성자가 있습니다.

public MyClass() { 
  this(new Client1(), new Client2()); 
}

public MyClass(Client1 client1, Client2 client2) { 
  this.client1 = client1; 
  this.client2 = client2; 
}

2

삽입 된 종속성을 사용할 수있을 때까지 "getter create"관용구를 고려할 수 있습니다.

예를 들어

public class Example {
  private MyObject myObject=null;

  public MyObject getMyObject() {
    if (myObject==null) {
      myObject=new MyObject();
    } 
    return myObject;
  }

  public void setMyObject(MyObject myObject) {
    this.myObject=myObject;
  }

  private void myMethod() {
    if (getMyObject().doSomething()) {
      // Instance automatically created
    }
  }
}

그러면 getter를 통해 myObject를 참조 할 수 있도록 내부적으로 리팩토링 할 수 있습니다. 전화 할 필요가 없습니다 new. 앞으로 모든 클라이언트가 의존성 주입을 허용하도록 구성되면 생성 코드를 한곳에서 제거하면됩니다. setter를 사용하면 필요에 따라 모의 객체를 주입 할 수 있습니다.

위의 코드는 예제 일 뿐이며 내부 상태를 직접 노출합니다. 이 접근 방식이 코드베이스에 적합한 지 신중하게 고려해야합니다.

또한 주요 리팩토링의 성공을위한 유용한 팁이 포함 된 " 레거시 코드로 효과적으로 작업하기 "를 읽는 것이 좋습니다 .


감사. 어떤 경우에는이 접근법을 고려하고 있습니다. 그러나 MyObject의 생성자가 클래스 내부에서만 사용할 수있는 매개 변수가 필요한 경우 어떻게해야합니까? 또는 수업 기간 동안 둘 이상의 MyObject를 작성해야 할 때? MyObjectFactory를 주입해야합니까?
JW01

@ JW01 클래스에서 내부 상태를 읽고 맞추기 위해 getter 작성자 코드를 구성 할 수 있습니다. 수명 동안 둘 이상의 인스턴스를 작성하는 것은 까다로울 수 있습니다. 그러한 상황에 처한 경우, 해결 방법보다는 재 설계를 제안합니다. 게터를 너무 복잡하게 만들지 마십시오. 목 주위에 맷돌이 될 것입니다. 당신의 목표는 리팩토링 프로세스를 용이하게하는 것입니다. 너무 어려워 보이면 다른 영역으로 이동하여 문제를 해결하십시오.
Gary Rowe

그것은 싱글 톤입니다. 싱글 톤을 사용하지 마십시오 O_O
CoffeDeveloper

@DarioOO 사실 그것은 매우 열악한 싱글 톤입니다. 더 나은 구현은 Josh Bloch에 따라 열거 형을 사용합니다. 싱글 톤은 유효한 디자인 패턴이며 용도가 있습니다 (고가의 일회성 제작 등).
게리 로우
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.