단위 테스트의 세 가지 스타일

  • 단위 테스트의 세 가지 스타일
    • 출력 기반 테스트
    • 상태 기반 테스트
    • 통신 기반 테스트
  • 테스트 품질: 출력 기반 > 상태 기반 > 통신 기반

출력 기반 테스트 정의

  • SUT에 입력을 넣고 생성되는 출력을 점검하는 방식
  • 출력 기반 테스트 스타일은 사이드 이펙트가 없는 순수 함수 방식으로 작성된 코드에만 적용된다.

상태 기반 스타일 정의

  • 작업이 완료된 후 시스템 상태를 확인
  • 상태는 SUT나 협력자 중 하나, 또는 데이터베이스나 파일 시스템 등과 같은 프로세스 외부 의존성의 상태 등을 의미할 수 있다.

통신 기반 스타일 정의

  • 목을 사용해 테스트 대상 시스템과 협력자 간의 통신을 검증한다.
  • 고전파는 통신 기반 스타일보다 상태 기반 스타일을 선호한다. 런던파는 통신 기반 스타일을 선호한다. 출력 기반 테스트는 두 분파 모두 사용한다.

단위 테스트 스타일 비교

회기 방지와 피드백 속도 지표로 스타일 비교하기

  • 회기 방지 지표는 세 가지 특성으로 결정된다.
    • 테스트 중에 실행되는 코드의 양
    • 코드 복잡도
    • 도메인 유의성
  • 어떤 스타일도 위 세 가지 특성에 차이가 없다.
  • 테스트 피드백 속도도 테스트 스타일과 차이가 없다.
    • 목은 런타임에 지연 시간이 생기는 편이므로 통신 기반 테스트가 약간 나쁠 수 있지만 큰 차이가 나지 않는다.

리팩터링 내성 지표로 스타일 비교하기

  • 리팩터링 내성은 리팩터링 중에 발생하는 거짓 양성 수에 대한 척도다.
  • 출력 기반 테스트는 테스트가 테스트 대상 메서드에만 결합되므로 거짓 양성 방지가 가장 우수하다.
  • 상태 기반 테스트는 일반적으로 거짓 양성이 되기 쉽다. 테스트와 제품 코드 간의 결합도가 클수록 유출되는 구현 세부 사항에 테스트가 얽매일 가능성이 커진다.
  • 통신 기반 테스트가 가장 취약하다. 테스트 대역으로 상호 작용을 확인하는 테스트는 대부분 깨지기 쉽다.
  • 통신 기반 테스트도 캡슐화를 잘 지키고 테스트를 식별할 수 있는 동작에만 결합하면 거짓 양성을 최소로 줄일 수 있다.

유지 보수성 지표로 스타일 비교하기

  • 유지 보수성은 단위 테스트의 유지비를 측정하며, 다음 두 가지 특성으로 정의한다.
    • 테스트를 이해하기 얼마나 어려운가?
    • 테스트를 실행하기 얼마나 어려운가?
  • 출력 기반 테스트는 거의 항상 짧고 간결하므로 유지 보수가 쉽다. 전역 상태나 내부 상태를 변경할 리 없으므로, 프로세스 외부 의존성을 다루지 않는다.
  • 상태 기반 테스트는 일반적으로 출력 기반 테스트보다 유지 보수가 쉽지 않다. 출력 검증보다 더 많은 코드가 발생한다.
    • 상태 기반 테스트의 유지 보수성을 위한 2가지 방법
      • 헬퍼 메서드 사용
      • 값 객체로 클래스 변환하기
  • 통신 기반 테스트는 유지 보수성이 가장 낮다. 테스트 대역과 상호 작용 검증을 설정해야 하며, 이는 공간을 많이 차지한다.
    • 목이 사슬 형태로 있을 때 테스트는 더 커지고 유지 보수하기가 어려워진다.

스타일 비교하기: 결론

출력 기반상태 기반통신기반
리팩터링 내성을 지키기 위해 필요한 노력낮음중간중간
유지비낮음중간높음
  • 출력 기반 스타일은 함수형으로 작성된 코드에만 적용할 수 있고, 대부분의 객체지향 프로그래밍 언어에는 해당하지 않는다.
  • 상태 기반 테스트와 통신 기반 테스트를 출력 기반 테스트로 변경하는 기법이 있다.

함수형 아키텍처 이해

함수형 프로그래밍이란?

  • 함수형 프로그래밍: 수학적 함수(또는 순수 함수)를 사용한 프로그래밍

  • 수학적 함수: 숨은 입출력이 없는 함수

  • 숨은 입출력 유형

    • 사이드 이펙트: 사이드 이펙트는 메서드 시그니처에 표시되지 않는 출력이며, 따라서 숨어있다. 연산은 클래스 인스턴스의 상태를 변경하고 디스크의 파일을 업데이트하는 등 사이드 이펙트를 발생시킨다.
    • 예외: 메서드가 예외를 던지면 ,프로그램 흐름에 메서드 시그니처에 설정된 계약을 우회하는 경로를 만든다.
    • 내외부상태에 대한 참조: DateTime.Now와 데이터베이스의 데이터 질의 등 메서드 시그니처에 없는 실행 흐름에 대한 입력이며, 따라서 숨어있다.
  • 사이드 이펙트 예시

    • 겉으로 수학적 함수처럼 보이지만, comments.add(comment) 가 숨은 출력에 해당한다.
fun addComment(text: String): Comment {  
    val comment = Comment(text)  
    comments.add(comment)  
    return comment  
}

함수형 아키텍처란?

  • 함수형 프로그래밍의 목표: 사이드 이펙트를 완전히 제거하는 것이 아니라 비즈니스 로직을 처리하는 코드와 사이드 이펙트를 일으키는 코드를 분리하는 것
  • 두 가지 코드 유형을 구분해서 비즈니스 로직과 사이드 이펙트를 분리한다.
    • 함수형 코어(결정을 내리는 코드): 사이드 이펙트가 필요없기 때문에 수학적 함수를 사용한다.
    • 가변 셸(해당 결정에 따라 작용하는 코드): 수학적 함수에 의해 이뤄진 모든 결정을 데이터베이스의 변경이나 메시지 버스로 전송된 메시지와 같이 가시적인 부분으로 변환한다.
  • 가변 셸은 모든 입력을 수집한다.
  • 함수형 코어는 결정을 생성한다.
  • 셸은 결정을 사이드 이펙트로 변형한다.
  • 테스트 목표: 출력 기반 테스트로 함수형 코어를 두루 다루고 가변 셸은 훨씬 더 적은 수의 통합 테스트에 맡긴다.

함수형 아키텍처와 육각형 아키텍처 비교

  • 함수형 아키텍처와 육각형 아키텍처 공통점
    • 결정과 실행을 분리
    • 의존성 간의 단방향 흐름
  • 함수형 아키텍처와 육각형 아키텍처 차이점
    • 함수형 아키텍처: 모든 사이드 이펙트를 불변 코어에서 가변 셸로 밀어낸다.
    • 육각형 아키텍처: 모든 수정 사항은 도메인 계층 내에 있어야 하며, 계층의 경계를 넘어서는 안 된다.

함수형 아키텍처와 출력 기반 테스트로의 전환

  • 리팩터링 단계
    • 프로세스 외부 의존성에서 목으로 변경
    • 목에서 함수형 아키텍처로 변경

함수형 아키텍처의 단점 이해하기

  • 성능 단점
    • 함수형 코어에서 공유 의존성을 사용하지 않도록 리팩토링 하는 과정에서 성능저하가 발생할 수 있다.
    • 성능 영향이 그다지 눈에 띄지 않는 일부 시스템에서는 함수형 아키텍처를 사용해 유지 보수성을 향상시키는 편이 낫다.
  • 코드베이스 크기 증가
    • 함수형 아키텍처로 바꾸기에 코드베이스가 너무 단순하거나 비즈니스 관점에서 중요하지 않다면, 함수형 아키텍처를 사용하는 것에 별 의미가 없다.
    • 함수형 방식에서 순수성에 많은 비용이 든다면, 도메인 모델을 불변으로 할 수 없기 때문에 출력 기반 테스트에만 의존할 수 없다.
      • 대부분 출력 기반 스타일과 상태 기반 스타일을 조합하며, 통신 기반 스타일을 약간 섞어도 괜찮다.