Lined Notebook

Unit Testing 책 정리 - 2

by Understand

이전 Unit Testing 책 정리 - 1 에서는 좋은 단위 테스트에 대해서 요약했다. 이번에는 실질적인 방법에 대해 다루는 5단원 이후의 내용을 읽고 내 나름대로의 생각으로 정리해보았다.

 

이전에 요약한 내용 중에 가장 중요한 키워드를 뽑으라면 리팩토링 내성이다. 코드가 바뀌더라도 테스트 코드가 유효하도록 만드는 것이 중요하다는 것이다. 이를 위해서 구현 세부사항을 테스트하는 것을 포기하고, 기능 중심으로 테스트하는 것이 좋다는 것을 말하고 있다. 이후 내용에서도 리팩토링 내성을 지키기 위해 어떻게 메인 코드를 작성하고 테스트해야하는지 등에 대한 내용을 이야기하고 있다.

 

어떤 코드를 테스트 해야하는가

5장 목과 테스트 취약성에서는 제목과는 다르게 관심사의 분리 (Separation Of Concern)를 중심으로 어떤 코드를 테스트 해야하는지를 설명한다. 결론부터 말하면 클라이언트가 직접적으로 사용하는 코드는 테스트하고, 그렇지 않은 코드는 테스트 하지 않는다.
이때 클라이언트가 직접적으로 사용하는 연산 혹은 상태관련 코드를 식별할 수 있는 동작라고 부르고, 그렇지 않은 코드를 구현 세부사항이라고 부른다. 책에서는 좋은 코드는 식별할 수 있는 코드는 클라이언트에게 공개되어 있고 모든 구현 클라이언트에게 노출이되지 않은 코드라고 말한다.


이러한 분리 혹은 캡슐화를 하기 위해 Hexagonal Architecture를 소개한다. Hexagonal Architecture에 대한 배경은 링크해둔 블로그에서 자세히 설명해준 것 같아 간단하게만 말하면, 도메인간 분리, 그리고 비즈니스 로직과 Interface와의 분리를 위해 제안되었다. 이를 위해 코드를 도메인별로 나누고, 각 도메인별 비즈니스 코드와 Interface를 계층적으로 분리해둔 것이다. 이때 비즈니스 코드는 세부 구현사항이므로 목을 사용하지 않고, 외부 application과 Interface간 통신에서만 목을 사용하는 것을 추천한다.

 

그 외에도 테스트 대역을 목과 스텁으로 구분한다. 목은 테스트 대상 코드에서 외부 의존성으로 나가는 상호 작용을 모방하는 테스트 대역이고, 스텁은 외부 의존성에서 테스트 대상 코드로 오는 상호작용을 모방한 테스트 대역이다. 책에서는 외부에서 들어오는 입력데이터와 같은 상호작용은 구현 세부사항으로 보고 스텁을 사용하지 않는 것을 권장한다.

 

단위 테스트

6장 단위 테스트 스타일에서는 테스트를 출력 기반 테스트, 상태 기반 테스트, 통신 기반 테스트로 구분한다. 이때 출력 기반 테스트를 가장 선호한다. 구현 세부사항에 거의 결합되지 않아 리팩토링 내성이 있고, 작고 간결해 유지보수하기도 쉽기 때문이다. 상태 기반 테스트나 통신 기반 테스트는 모두 크기가 크고 유지보수가 어렵다. 상태 기반 테스트와 통신 기반 테스트에서 출력 기반 테스트로 변환할 수 있는 방법도 있다. 기존 코드에서 명령과 쿼리를 분리하는 것이다 (Command Query Separation. 이때 함수형 프로그래밍 이야기가 나온다. 함수형 프로그래밍이 side effect를 관리하기 좋아 유지보수는 좋지만 필요 이상으로 코드가 많아지고 성능을 희생하게 된다. 책에서도 모든 코드를 함수형 프로그래밍으로 바꾸는 것이 아니라 필요한 부분만 사용하는 것을 권한다.

 

가치있는 테스트를 위한 리팩토링

이전 블로그에서 테스트는 가치있는 코드에 대해서만 하면 된다고 하였다. 7장 가치있는 테스트를 위한 리팩토링은 가치있는 코드는 어떤 코드이고, 이를 다른 코드와 분리하기 위해 어떻게 리팩토링하는지에 대한 내용이다. 코드는 (복잡도 및 도메인 유의성, 협력자) 두가지 기준으로 총 4가지로 분류할 수 있다.

 

코드의 네가지 유형

 

간단한 코드는 테스트할 필요가 없다. 가치가 0에 가깝기 때문이다. 또한 컨트롤러는 통합테스트로 테스트를 진행하면된다. 도메인 모델 및 알고리즘은 단위테스트시 노력대비 가치가 가장 높은 코드이다. 가장 문제가 되는 코드는 지나치게 복잡한 코드이다. 테스트가 필요하지만 테스트하기 너무 어렵다. 따라서 이런 코드를 리팩토링해서 일부는 알고리즘으로 나머지는 컨트롤러로 뺴는 것이 일반적이다.

 

이를 위한 방법론으로는 humble 객체 패턴을 소개한다.간단하게 말하면 테스트하기 어려운 의존성들(오케스트레이션)과 비즈니스로직을 분리하고 이를 연결하는 새로운 객체를 만드는 것이다. 비즈니스로직과 오케스트레이션을 분리할때는 세가지 중요한 특성이 있다.

  • 도메인 모델 테스트 유의성: 도메인 클래스 내 협력자 수와 유형에 대한 함수
  • 컨트롤러 단순성: 컨트롤러에 의사 결정 지점이 있는가
  • 성능: 외부 의전송에 대한 호출 수

하지만 이 중 최대 두가지만 만족할 수 있다.

  • 외부에 대한 모든 읽기와 쓰기를 비즈니스 연산 가장자리로 밀어내기: 성능저하
  • 도메인 모델에 프로세스 외부 의존성 주입: 도메인 모델 테스트 유의성이 떨어짐
  • 의사 결정 프로세스 단게 더 세분화하기: 컨트롤러가 복잡해짐

의사 결정 프로세스를 더 세분화하는 것이 가장 효과적인 절충이다. CanExecute / Execute 패턴과 을 통해 컨트롤러 복잡도 증가를 완화 시킬 수 있다.

 

마지막으로 이번 챕터에서는 중간 중간 테스트 팁이 나오는데 유용한 것 같아 따로 정리해보았다.

  • 코드가 더 중요해지거나 복잡해질수록 협력자는 더 적어야 한다.
  • 좋지 않은 테스트를 작성하는 것보다는 테스틀르 전혀 작성하지 않는 편이 낫다
  • 추상화할 것을 테스트하기 보다는 추상화를 테스트한느 것이 더 쉽다 (실제 구현체를 테스트하는 것보다 해당 구현체의 추상화된 인터페이스로 테스트하는 것이 더 쉽다)

 

Reference

블로그의 프로필 사진

블로그의 정보

BookStoreDiary

Understand

활동하기