테스트 가능 설계

이승한·2020년 1월 20일
0

제품 코드는 단위 테스트를 쉽고 빠르게 작성할 수 있도록 설계해야 한다. 테스트 가능 설계는 테스트코드에서 클래스를 생성하고, 구현 일부를 대체하고, 다른 시나리오를 시뮬레이션하고, 원하는 실행경로를 선택하는 등의 작업을 쉽게할 수 있도록 해준다.
테스트 용이성이 떨어질 수록 테스트를 작성하는 프로그래머의 부담이 커진다.

모듈러 설계

  • SOLID 설계 원칙
  • 맥락을 고려한 모듈러 설계
    • 모듈을 조합하여 시스템을 구성할 수 있게 하는 것은 물론 중요하다. 하지만 당장은 그 시스템이 아무리 크고 멋있어 보이더라도 언젠가 더 큰 시스템의 일부로 될 수 있도록 설계 해야 한다.
    • 당장 사용하려는 방식은 물론 향후의 확장 까지 고려해야 좋은 설계가 완성된다.
  • 모듈러 설계를 위한 시운전
    • 코드보다 테스트를 먼저 작성하면 확실히 API 의 사용자인 고객의 관점에서 바라보게 된다.

테스트 불가 원인

  1. 프로그래머가 원하는 것에 접근하지 못하기 때문.
  2. 특정 부분을 마음대로 교체하지 못하기 때문
  • 클래스 생성 불가
    • 자체를 생성할 수 없거나, 협력 객체를 만들 수 없는 경우
    • 접근 제한자를 너무 보수적으로 잡은 경우
    • 정적 초기화 블록 사용
  • 메서드 호출 불가
    • private 메서드를 호출하고 싶을 때가 있다.
    • 어떤 인자를 넣어야 할지 알쏭달쏭한 경우
  • 결과 확인 불가
    • 아무것도 반환하지 않는 void 메서드, 다른 협력 객체와 상호 작용 하는 메서드
    • 상호작용을 가로챌 방법이 없을 때가 있다. 협력 객체가 메서드 안에 묶여 테스트 더블로 교체할 수 없을 때.
    • 테스트 코드에서 스레드에 접근할 수단이 없는 경우
  • 협력 객체 대체 불가
    • 메서드 연쇄 호출
  • 메서드 오버라이딩 불가
    • 협력 객체 전부를 대체하기 보다는 일부 코드만 변경하고 싶을 때

테스트 가능 설계를 위한 지침

  • 복잡한 private method 를 피하라.
    • private method 는 테스트할 필요가 없도록 만들어 져야 한다.
    • public method 의 가독성을 높이기 위한 간단한 유틸리티로 제한되어야 한다.
  • final method 를 피하라.
    • final 이 선언해야할 합리적 사유는 실행 도중 외부 클래스를 로딩하거나 옆의 동료를 믿지 못할 때 뿐이다.
    • 성능 개선만을 위해서라면 메서드나 클래스를 final 로 선언하지 않는다. 성능 저하 문제를 발견한 후에라야 한번쯤 고려해볼만 하다.
  • 정적 메서드를 피하라.
    • 단위 테스트에서 언제가 스텁으로 바꿔야 할 듯한 메서드는 정적 메서드로 만들지 않는다.
    • 정적 메서드는 만들기는 쉽지만 스텁으로 교체하고 싶을 때에는 꽤 속을 썩인다.
  • new 는 신중하게 사용하라
    • new 는 정확한 구현이 그것이라고 못 박는 행위다. 따라서 메서드 본문에서는 테스트 더블로 대체할 가능성이 없는 객체만 직접 생성해야 한다.
  • 생성자에서는 로직 구현을 피하라.
    • 생성자중 하나를 호출할 수밖에 없으므로 생성자는 절대 무시할 수 없는 존재다.
    • 생성자의 코드를 모두 protected 로 바꾸면 하위 클래스에서 오버라이딩 할수 있으니 훨씬 났다.
    • 생성자는 단위 테스트에서 교체해야할 코드는 절대 넣어서는 안된다. 만약 이런 코드를 발견하면 메서드로 추출하거나 외부 객체형태로 입력 받게끔 수정하여 테스트에서 원하는대로 바꿔칠 수 있도록 해야 한다.
  • 싱글톤을 피하라
    • 싱글톤은 테스트가 자신에게 필요한 대용품을 만들 수 없게 가로막기도 한다.
    • getInstance 가 클래스가 아닌 인터페이스를 반환하는 것을 추천한다.
    • 소문자 s 싱클톤이다. 객체가 하나만 만들어진다는 장치적 보장없이 운영 시스템에서는 하나만 만들기로 팀원간 합의하는 것 이다.
  • 상속보다는 Compsition 을 사용하라.
    • 상속의 용도는 다형성이지 재사용이 아니다.
    • 한 클래스의 자식이 되었으니 자연스럽게 다른 클래스를 부모로 모실 기회는 사라진다.
    • 기능 재활용을 위한 목적이라면 컴포지션 방식이 낫다.
  • 외부 라이브러리를 감싸라
    • 라이브러리의 테스트 용이성에 신경쓰자. 그리고 문제가 될 것 같다면 직접 다른 구현으로 교체하기 쉽고 테스트하기도 편한 인터페이스를 하나 만들어서 감싸자.
  • 서비스 호출을 피하라
    • 서비스 호출 방식은 생성자 주입 방식보다 스텁으로 대체하기 훨씬 어렵다.
    • 테스트에서 대용품으로 교체할 방법이 없는건 아니지만, 작업량이 많다.

SOLID 설계 원칙

객체지향 설계 원칙은 테스트 용이성과도 잘 어울린다. 그리고 코딩 시 이런 설계 원칙을 잘 지켜주면 모듈러 설계가 될 가능성이 상당히 높아진다.

단일 책임 원칙

클래스를 수정하는 이유는 오직 하나뿐 이어야 한다.
메서드를 수정하는 이유도 하나뿐 이어야 한다.

개방 패쇄 원칙

확장을 위해서는 개방적이되 수정에 대해서는 폐쇄적 이어야 한다.
코드 수정 없이도 클래스의 기능을 변경할 수 있도록 하자는 이야기다.

리스코프 치환 원칙

상위 클래스는 하위클래스로 대체될 수 있어야 한다.
코드 재사용을 편하게 할 요량을 만들어진 계층구조는 사라진다.
잘 따른다면 계약 테스트가 가능하여 테스트 용이성이 높아진다.

인터페이스 분리 원칙

하나의 범용 인터페이스 보다는 쓰임새별로 최적화된 인터페이스 여러개가 낫다.

의존관계 역전 법칙

코드는 구현체가 아닌 추상 개념에 종속되어야 한다.
대상 코드를 오버라이딩 하지 않고도 기능을 변경할 수 있다.

profile
software develop engineer

0개의 댓글