[소프트웨어 테스트] 07. 테스트 가능성을 위한 설계
[소프트웨어 테스트] 07. 테스트 가능성을 위한 설계
소프트웨어 테스트 시리즈의 글입니다.
테스트하기 쉬운 코드를 설계하는 방법
7.1 도메인 코드에서 인프라 코드를 분리
- 도메인 : 시스템의 핵심이 있는곳
- 인프라: 외부 의존성. (ex. 웹서비스 호출, 파일 읽기 쓰기, DB..) ⇒ 가능한 인프라코드를 분리해 테스트에 방해가 되지 않도록 해야함 단일 책임을 지고 인프라코드가 전혀 없는 클래스를 테스트 하는게 훨씬 쉽다 ⇒ 명확한 책임분리가 중요
- 헥사고날 아키텍쳐 (포트+어댑터 패턴)
- 애플리케이션은 직접 외부시스템과 상호작용하지않음
- 포트를 이용해서 통신함
- 포트는 통신 기술과 무관해야함
- 애플리케이션관점ㄷ에서 포트는 통신방법의 세부사항을 추상화함
- 즉, 애플리케이션은 외부 통신방법에 대해 알 필요가 없음
- 어댑터 테스트는? 통합테스트로. ⇒ 주요 원칙은 결국 인프라를 비즈니스코드로부터 분리하는것.
7.2 의존성 주입과 제어 가능성
클래스는 완전히 제어할수있어야하고 관찰할수있어야함 외부 인프라모듈에 의존하는 클래스의 경우, 해당 의존성을 주입할 수 있도록 만들어야함.
- 생산자로 의존성 받기
- setter사용 의존성을 주입하면 아래와 같은 장점이 있음
- 의존성을 모의객체나 스텁으로 만들어 주입할수있음. → 테스트생산성 증대
- 의존성 명확하게해줌
- 관심사를 분리해줌
- 클래스가 더 확장성을 가지게됨
헥사고날 아키텍처는 인프라-클래스가 가지게될 추상적 상호작용을 나타내는 인터페이스(=포트)를 통해 관심사를 더 잘 분리하고 계층간의 결합을 줄이고 계층간 상호작용을 단순히 할 수 있음 어댑터는 추상적으로 정의된 인터페이스(=포트)에 의존. ⇒ 의존성 역전의 원칙
- 고수준 모듈은 저수준 모듈에 의존 X
- 추상화는 세부사항에 의존 X
7.3 클래스 및 메서드를 관찰 가능하게 하기
관찰가능성 : 기능이 기대했던 대로 동작하는지를 얼마나 쉽게 단언할 수 있는가? 관찰 가능성을 높히려면? ⇒ 의존성을 모의하고 상호작용을 단언할 수 있도록 함
7.3.1 단언을 보조하는 메서드 도입
클래스행위를 단언하기 위한 스파이가 왜 필요한지 살펴보기 → 더 쉬운방법은? ex. 상태값을 나타내는 getter함수 추가.
7.3.2 void 메서드 행위 관찰
void 메서드는 관찰이 자연스럽지 않음. → 무엇을 단언해서 테스트하지? ex. ArgumentCaptor 사용. ex. getter 함수 추가.
위의 방법이 정답은 아니고, 실용주의가 핵심.
7.4 의존성 전달 방법 : 클래스 생성자와 메서드 매개변수
생성자를 통해 의존성을 클래스에 전달하는 방법
- 복잡하지만 의존성을 스텁으로 만들기 쉬움
- 전체 클래스와 테스트의 복잡도가 약간 증가하지만 클라이언트 클래스는 단순해짐 메서드에 값을 직접 전달하는 방법
- 클래스를 호출하는 모든곳에서 인수를 제공해야함
- 클래스와 테스트는 단순해지지만 클라이언트가 복잡해짐 ⇒ 필자는 클라이언트의작업을 단순하게 하려고 노력함.
7.5 현업에서의 테스트 가능성 설계
개발 도중 테스트를 작성하면 코드 설계에 대한 힌트를 얻을 수 잇음. ⇒ 테스트가 코드 설계에 대한 피드백 제공
- 테스트는 테스트 대상 클래스의 인스턴스 생성
- 테스트는 테스트 대상 메서드 호출
- 테스트는 메서드가 기대한 동작을 하는지 단언 각 단계의 수행 난이도를 게속 살펴보고 더 쉽게 구현하는 방법을 고민.
7.5.1 테스트 대상 클래스의 응집도
응집도 : 어떤 요소든 단 하나의 책임을 가지는것을 뜻함. 여러 책임을 지닌 클래스(=응집도가 낮은 클래스)는 복잡하고 이해하기 어려움.
7.5.2 테스트 대상 클래스의 결합
응집력 있는 클래스를 사용하면 여러 클래스를 조합해서 큰 행위를 구성하게됨. 그러나 결합도가 높은 설계를 하면 진화를 헤칠 수 있음. ⇒ 가능한 적게 클래스가 결합되도록 해야함
- 테스트에 수많은 의존성 인스턴스가 필요하다면 나쁜 징후.
- A 테스트 중 실패했는데 문제는 B 에있는 경우. 설계오류를 확인해야함.
7.5.3 복잡한 조건과 테스트 가능성
매우 복잡한 조건을 테스트하려면 노력이 필요. 복잡한 조건을 여러 작언 조건으로 분할하는 방식 추천.
7.5.4 private 메서드 테스트 가능성
private메서드 테스트해야하나? ⇒ 대게 private 메서드의 응집도가 떨어지고 복잡함 메서드를 추출하고 테스트하는 리팩토링을 고려해야함.
7.5.5 적적메서드, 싱글턴, 테스트 가능성
정적메서드는 테스트 가능성에 악영향을 미침. 가능하면 안만드는게 좋음. 추상화를 그 위에 추가하는것이 더 좋은 결정. 외부 종속성이 필요한경우 동일한 권장사항이 적용됨. 싱글턴 패턴 역시 테스트 가능성 저하. (⇒ 전역변수처럼 동작.)
7.5.6 육각형 아키텍처와 설계 기법으로서의 모의객체
도메인 개발 후, 다른곳에서 필요할때마다 ‘포트’를 드러냄. 포트 = 어댑터의 구현을 신경쓰지않고 나머지 기능을 개발할수있게해줌 어플리케이션의 ‘계약’을 설계에 반영, 최선의 인터페이스를 모델링 → 구현중인 클래스에 집중 가능. 당장은 관심 없는것들을 추상화 가능.
7.4.7 테스트가능성을 위한 설계에 대한 추가자료
- 레거시 코드 활용 전략
- 테스트 주도 개발로 배우는 객체지향 설계와 실천
- 클린 아키텍처