Eternity's Chit-Chat

aeternum.egloos.com



의존성 끊기와 단위 테스트 – 1부 Software Quality

이 글은 제가 마이크로소프트웨어의 수퍼 개발자의 꿈이라는 컬럼에 연재했던 "의존성 끊기와 단위테스트"라는 글입니다. 여러가지로 미흡한 글이지만 좀 더 많은 분들이 봐주셨으면 하는 마음에 블로그에 원글을 게제합니다.

단위 테스트 표류기

최근 몇 년 동안 소프트웨어 개발 방식은 혁신적인 전환점을 맞이하게 되었다. 과거의 무겁고 형식적인 프로세스 중심의 개발 방식을 벗어나, 점차 소프트웨어와 사람에 초점을 맞추는 기민하고 적응적인 개발 방식을 채택하는 조직이 늘어나고 있다. 이와 함께 소프트웨어를 개발하는 방식 역시 커다란 변화를 맞이하게 되었는데 그 중 가장 주목할만한 점은 단위 테스트(Unit Test)가 소프트웨어 개발 프로세스의 핵심 요소로 자리를 잡았다는 점이다.

단위 테스트의 핵심 아이디어는 소프트웨어를 구성하는 개별적인 클래스를 고립시킨 상태에서 테스트하는 것이다. 문제는 테스트를 수행하기 위해 클래스를 고립시킨다는 것이 말처럼 간단하지 않다는 점이다. 객체-지향 시스템 안에서 숨쉬고 있는 객체들은 다른 객체들과의 긴밀한 협력 관계를 통해 자신의 역할을 수행한다. Order 객체를 테스트하기 위해서는 Order 객체와 연관 관계를 맺고 있는 Customer 객체, OrderLineItem 객체, Product객체가 필요하다. 끝없는 연관 관계의 미로 속을 헤매다 정신을 차려 보면 작은 단위 테스트 안에 커다란 객체 그래프를 몰골 사납게 꾸겨 넣은 채 끙끙거리고 있는 자신을 발견하게 된다.

<그림 1> 객체 간의 의존성은 얽히고 설킨 사슬과 같다.

그렇다면 단위 테스트에서 클래스를 고립시키는 것이 중요한 이유가 무엇일까? 일반적으로 커다란 객체 그래프를 대상으로 수행되는 테스트는 다음과 같은 문제점을 지니고 있다.

  • 에러 위치 확인(Error Localization) - 테스트가 실패할 경우 거대한 객체 그래프의 어디에서 에러가 발생했는지 확인하기 어렵다. 여러 클래스를 가로지르는 실행 경로를 살펴 보면서 입력 값과 출력 값을 추적하는 기나긴 여정 속에서 테스트에 대한 애정이 조용히 사라지는 것을 느끼게 된다. 클래스를 고립시키면 상대적으로 제한된 부분만 확인하면 되므로 에러의 원인을 파악하기가 쉽다.
  • 실행 시간(Execution Time) - 당연한 이야기지만 거대한 객체 덩어리를 테스트하는 것은 소수의 객체를 테스트하는 것보다 오랜 시간이 소요된다. 만약 내부의 특정 객체가 데이터베이스와 같은 외부 리소스에 의존하고 있다면 수행 시간은 기하급수적으로 늘어난다. 테스트 실행 시간이 길어지면 길어질수록 개발자들이 테스트를 수행하는 횟수가 줄어들며 결과적으로 건강한 피드백 루프의 장점이 손상된다.
  • 테스트 커버리지(Coverage) - 거대한 객체 그래프의 특정 코드를 수행하기 위해 필요한 입력 값을 찾는 것보다는 특정 클래스에 포함된 단일 메소드를 실행하기 위해 필요한 입력 값을 찾는 것이 쉽다. 따라서 테스트 대상 클래스를 고립시킬수록 높은 테스트 커버리지를 얻을 수 있다.

단위 테스트의 효과를 극대화시키기 위해서는 클래스를 고립시켜야 한다. 따라서 클래스와 클래스 간의 의존성을 끊기 위한 기법이 필요하다.

의존성 끊기(Breaking Dependency) 게임
의존성(dependency)이란 “두 요소 간의 관련성으로 한 요소에 대한 변경이 다른 요소가 필요로 하는 정보를 제공하거나 다른 요소가 제공하는 정보에 영향을 주는 관계”를 의미한다. 즉, 어떤 요소의 변경이 다른 요소에 영향을 미친다면 두 요소 간에 의존성이 존재한다고 말한다. 클래스가 다른 클래스의 인스턴스를 속성으로 포함하는 경우, 메소드의 파라미터로 사용하는 경우, 메소드 내부에서 지역적으로 인스턴스를 생성하는 경우, 다른 클래스를 상속받는 경우 모두 두 클래스 간에 의존 관계의 형성된다.

앞에서 살펴본 바와 같이 단위 테스트의 핵심은 개별 클래스를 고립시키는 것이다. 단위 테스트를 수행하기 위해 Order 클래스의 인스턴스를 생성해야 하는데, Order 클래스가 Customer, OrderLineItem, Product에 의존하고 있다면 의존하고 있는 모든 객체들을 포함하는 거대한 객체 그래프를 생성해야 할까?

결국 단위 테스트란 의존성 끊기 게임이다. 단위 테스트의 가장 큰 적은 클래스 간의 의존성이다. 개별 클래스를 단위 테스트하기 위해서는 객체 그래프 상의 적절한 위치에서 의존성을 제어할 필요가 있다. 

<그림 2> 단위 테스트를 위해 최소한의 의존성만을 남겨둔 채 객체를 고립시켜라.


덧글

  • 핼리 2016/10/31 23:04 # 삭제

    언제나 잘 읽고 갑니다.
※ 로그인 사용자만 덧글을 남길 수 있습니다.