# Unit test와 의존성 분리를 위한 디자인 패턴
TIP
구 블로그에 남겨둔 기록을 옮겨왔다.
결론부터 얘기하자면, GoF의 자바 디자인 패턴 25개(?)중 Strategy 패턴을 사용하라는 것.
Java에서 DI를 들어본 적이 있는가? Dependency Injection이라고 해서, 어떤 객체에 속하는 멤버 객체를 코드상에서 할당하는게 아니라 외부 xml 설정등으로 설정하는 방법이다. 한글로 의존성 주입이라고도 한다. 무슨말이냐면...
Toy.java
class Toy {
private AAA_Battery battery = new Energizer_AAA();
}
2
3
4
5
Toy를 만들때 특정 조건에 따라 AAA_Battery가 변경되어야 할 경우가 있을텐데, 위처럼 코드상에서 설정하는게 아니라
Toy.java
class Toy {
@battery
private AAA_Battery battery;
}
2
3
4
5
6
battery_config.xml
<prop name="battery">Energizer_AAA</prop>
2
3
같은 형태로 실제 코드에서 battery를 할당하지 않고, 소스가 아닌 외부 설정파일 등에서 연결(wiring)해주는 방식이다. 왜 이렇게 하느냐면, 소스를 변경하지 않고, 설정을 변경하겠다는 의미이다. 소스는 이미 컴파일되어서 나오기 때문에, battery를 변경하고자 한다면 소스를 변경하고 다시 컴파일 해야 한다. 하지만 이 코드는 소스 컴파일 없이 외부 설정파일만 변경하면 된다.
컴파일 안하려고 이런짓이 필요한가? 그것뿐만은 아니다. 스프링 프레임워크가 이런 방식을 사용한다. 이 DI 덕분에 스프링 처음 배울때 많은 사람들이 혼란을 가진다. 알고나면 장점이 보이지만 그전에는 쓸데없이 복잡하다고 느낀다. 자바 언어만 배워도 모자랄 판에 자바 문법과 맞지 않아 보이는 프레임워크까지 배우라니.. 도대체 battery 변수는 어디서 설정되는거지? 왜 null이 아닌거지? 이런 질문들이 해결되기까지는 많은 시간이 필요하다.
그럼 이런 질문이 들게 된다. 도대체 왜 저런 복잡한 짓을 하는거지? 이것도 역시 쉽게 이해하기는 어려운 부분이다. 만일 Toy 클래스를 테스트하려고 한다면, Toy에서 사용되는 battery의 인스턴스에 따라서 Toy의 결과가 달라질 수 있다. 우린 battery와 무관하게 Toy를 테스트하고 싶지만, battery와의 의존성이 있기 때문에 테스트를 수행하기가 매우 어렵다. 특히나 DB 또는 네트워크 모듈이었다면 테스트는 거의 불가능하다. 이럴 경우 Mocking이라고 해서 battery를 가짜로 만들어서 대신 할당해 놓고 테스트를 하는 방법이 있다.
현실에서도 비슷한 경우가 많은데, 일례로 장난감의 동작 수명을 체크하고 싶다면, 건전지를 원하는 동일한 규격의 건전지로 모두 갈아 끼우고 테스트를 해야 동작 수명 비교가 가능할 것이다.
마찬가지로, 위의 첫번째 코드는 장난감에 건전지가 밀봉되어있어서 갈아끼울 수 없는 모양새고, 두번째 코드는 갈아끼울수는 있으나 갈아끼우는 방법이 전문가만 알 수 있는 방법이라고 비유할 수 있다.
그럼 우리가 스프링 같은 복잡한 프레임워크를 배우지 않고서도 할 수 있는 방법은? 아래 코드를 보자.
Parent.java
...
AAA_Battery battery = new Energizer_AAA();
Toy toy = new Toy(battery);
Toy.java
class Toy {
private AAA_Battery battery;
public Toy(AAA_Battery battery) {
this.battery = battery;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
이정도 패턴이면 매우 기본적인 클래스 생성 방법중 하나이다. 하지만 여기서 주목할 것은, Toy 클래스에서 battery 인스턴스의 생성을 분리했다는 점이다. 저 코드에서는 Toy.java는 수정하지 않고서도 Parent.java만 수정해서 battery를 변경할 수 있다. 결국 Toy와 Battery를 부품으로 생각하고, 그것을 모아서 조립하는 주체가 존재하는 구조이다. 결국 각각의 부품은 모듈화 구조가 되는 것이고 그것을 또 잘 조립해서 다루는것은 분리해서 다룰 수 있게 된다. 따로 테스트를 해보고 싶다면
Test.java
...
AAA_Battery battery = new Bexel_AAA();
Toy toy = new Toy(battery);
2
3
4
이런 형태로 battery만 변경해서 테스트 코드 작성도 가능해진다.
몇 년 전에 서버 프로그램 만들면서도 느낀 바인데, 이렇게 정리하고 보니 그떄 그 깨달은 바가 역시나 기초 원리에 포함되는 개념들이었다. I/O등 상태에 따라 결과가 변하는 부분은 의존성을 분리하도록 구조를 잡는 것이 의미가 있다고 본다. TDD뿐만 아니라 BDD, 아니면 그냥 팀원간 개발 분리 관점에서도 꼭 필수적인 구조라고 생각한다. 요즘 유행하는 함수형 언어의 패러다임과도 일맥상통하는 부분이다.
원래는 node.js 모듈을 작성하다가 유닛 테스트를 어떻게 할까 고민하다가 조사중 얻게 된 결론이다. GoF 책에 이런 디자인 패턴이 있을까 찾아보니 역시 있었다. 이런 패턴이 자바에서는 그냥 Strategy 패턴이라고 해서 프로그램의 일부 동작을 외부로 위임하는 형태로 활용 가능하다고 되어있는데, 이게 테스트나 의존성 분리와도 연결되는 개념이 되더라.
언어 외적인 프레임워크를 배우는 데에 회의론적인 입장이라서, 이런 개념을 정리해 놓는다. 물론 개발 언어마다 거의 표준처럼 사용되는 프레임워크는 배워야겠지만, 기본 언어가지고도 활용할 수 있는 방안이 있고 생산성에 저해되지 않는다면, 오히려 팀내 협업에서 이해도의 수준을 맞출 수 있는 좋은 방법일 수도 있겠다라고 생각한다. 역시 기초가 튼튼해야 새로운 언어를 배울때도 도움이 된다. GoF 책을 다시 한번 보자.