이 글은 <이펙티브 소프트웨어 테스팅, 마우리시오 아니시 지음>을 참고하여 작성하였습니다.
Chapter 7. 테스트 가능성을 위한 설계
어떤 시스템은 테스트하기 너무 힘들다. 그 소프트웨어 시스템은 테스트를 할 수 있도록 설계되어 있지 않기 때문이다. 이번 장에서는 테스트하기 좋은 시스템을 만들기 위해서는, 어떤 설계를 해야하는지 알아보자.
(테스트를 위해 시스템의 설계를 바꾸라는 의미가 절대 아니다. SOLID, 컴포넌트 원칙을 잘 지켰다면 대게 해당 시스템은 테스트하기도 쉽다. 저런 원칙을 어기고 복잡하게 얽히고 설킨 클래스를 만들었다면 그 설계가 잘못됨이 테스트 코드를 짤때 드러나는 것일 뿐이다. 왠지 테스트를 짜기 어려울 것이다.)
7.1 도메인 코드에서 인프라 코드를 분리하기
도메인은 시스템의 핵심이 있는 곳이다. 즉 모든 비즈니스 규칙, 로직, 엔티티와 같은 요소가 이에 해당된다. 테스트가 꼭 필요한 영역이라고 볼 수 있다.
반면에 인프라스트럭처는 외부 의존성을 다루는 코드와 관련이 있다. 알다시피 외부 의존성은 테스트하기 매우 힘들며, 사실 테스트가 필요없는 부분이기도 하다.
테스트하기 쉬운 그리고 테스트를 해야하는 도메인 코드와
테스트하기 어려운 그리고 테스트가 필요없는 인프라 코드가 섞여있으면,
시스템의 핵심 로직도 테스트하기 어려운 시스템이 된다.
그렇기 때문에, 도메인 코드에서 인프라 코드를 분리해야한다. 분리하는 방식으로 헥사고날 아키텍처를 많이들 추천한다. 핵심 도메인 코드는 포트를 뚫고 어댑터로 연결해, 외부 인프라와 소통하는 방식으로 아키텍처를 구성하는 것이다.

가장 중요한 점은 인프라를 비즈니스 코드로부터 분리하는 것이다. 바로 호출하면 얼마나 쉽게 구현할 수 있는지 안다. 하지만 이와 같은 유혹에 빠지지 말자. 테스트할 수 없는 코드는 미래의 우리를 더욱 힘들게 만들 것이다.
7.2 의존성 주입과 제어 가능성
의존성 주입을 통해서도, 비즈니스 코드와 인프라 코드를 분리할 수 있다. 코드로 보자.
# NO
class Service:
def __init__(self):
self.db = Database() # 의존성을 직접 생성
def get_data(self):
return self.db.query("SELECT * FROM users")
# YES
class Service:
def __init__(self, db):
self.db = db # 외부에서 의존성을 주입받음
def get_data(self):
return self.db.query("SELECT * FROM users")
아래 경우, 테스트시 모의 객체를 생성자 파라미터로 넣으며 자유롭게 조작이 가능하다. 테스트 코드를 작성하기 매우 쉬울 것이다.
7.3 클래스 및 메서드를 관찰 가능하게 하기
클래스 및 메서드를 관찰 가능하도록 해두면, 테스트도 덩달아 쉬워진다.
public class Cart {
private boolean ready = false;
public boolean isReady() {
return ready;
}
}
7.5 현업에서의 테스트 가능성 설계
테스트가 코드 설계에 대한 피드백을 제공한다
테스트를 작성하기 뭔가 어렵다면, 애초에 설계가 잘못된 것은 아닐지 돌아보자.
테스트 대상 클래스의 응집도
응집도란 아키텍처 상의 모듈, 클래스, 메서드 또는 어떤 요소든지 단 하나의 책임을 가지는 것을 뜻한다. 그리고 응집력있는 클래스를 구성하는 것이 중요하다. 그런데 만약,
1. 테스트 스위트가 지나치게 거대해졌다?
-> 클래스의 응집력이 없는 것은 아닌지 다시 생각해보자. 클래스가 맡은 책임이 과해진 것일 수도 있다.
2. 테스트가 계속 커진다?
-> 테스트 클래스는 일정 시점이 되면 안정화 상태로 들어간다. 근데 만약 계속 같은 테스트 클래스로 들어와서 테스트를 추가하고 있다면, 설계가 나쁘기 때문일 수 있다.
테스트 대상 클래스의 결합
클래스끼리의 결합은 낮을 수록 변경이 수월하며, 클래스끼리 커플링되지 않도록 신경을 써주는 것이 좋다. 테스트 코드를 통해 결합도가 높은 클래스를 발견할 수 있는데,
1. 클래스 A를 테스트하기 위해 주입해야 하는 의존성 인스턴스가 너무 많다면?
2. 클래스 A를 테스트하는 중에 클래스 B의 문제를 발견했다면?
설계를 한번 다시 돌아볼 필요가 있을 것이다.
복잡한 조건과 테스트 가능성
조건문이 너무 복잡하면 테스트하기 쉽지 않다. 여러 작은 조건으로 나누어 문제의 복잡성도 낮추고 테스트 가능성도 좋게하는 방식이 낫겠다.
private 메서드와 테스트 가능성
종종 private 메서드도 테스트하고 싶은 욕구가 들 수 있다. 이는 설계 관점에서, 이 private 메서드가 적절한 위치에 있지 않다는 시그널일 수도 있다.
정적 메서드, 싱글턴, 테스트 가능성
정적 메서드, 싱글턴은 테스트 가능성을 저하시킨다. 해당 방식을 사용하기 전에, 꼭 그 방식을 사용해야만 하는지 다시 생각해보자.
'개발 이야기 > [스터디] 테스트' 카테고리의 다른 글
[이펙티브 소프트웨어 테스팅] Chapter 9. 대규모 테스트 작성 (1) | 2024.09.10 |
---|---|
[이펙티브 소프트웨어 테스팅] Chapter 8. 테스트 주도 개발 (1) | 2024.09.10 |
[이펙티브 소프트웨어 테스팅] Chapter 6. 테스트 더블과 모의 객체 (1) | 2024.09.10 |
[이펙티브 소프트웨어 테스팅] Chapter 4. 계약 설계 (1) | 2024.09.08 |
[이펙티브 소프트웨어 테스팅] Chapter 3. 구조적 테스트와 코드 커버리지 (0) | 2024.09.08 |