블라디미르 코리코프 님의 "단위 테스트" 책을 정리한 포스팅입니다.
1. 리팩터링할 코드 식별하기
코드 유형
항목 | 설명 |
복잡도 | 코드 내 의사 결정 지점 수로 정의 |
도메인 유의성 | 코드가 프로젝트의 도메인에 대해 얼마나 의미 있는가 |
협력자 수 | 코드가 의존하는 객체의 수 |
- 가변 의존성 | 외부 의존성이 얼마나 자주 바뀌는지 |
- 외부 의존성 | 외부 시스템에 대한 의존성 |
험블 객체 패턴을 사용해 지나치게 복잡한 코드 분할하기
지나치게 복잡한 코드
- 복잡도 및 도메인 유의성도 높고 협력자 수도 많은 코드
- 테스트가 어렵고, 변경하기도 어려우며, 버그가 생기기 쉬움
예시) 프레임워크 의존성 (비동기)
더보기
@Service
public class NotificationService {
@Async
public void sendEmail(String email, String message) {
// 실제 이메일 전송 로직
System.out.println("Sending email to " + email + ": " + message);
}
}
- 순수 Java 환경이나 단위 테스트에서는 작동하지 않음
-
- @Async는 Spring에 의존
- Spring 프레임워크 없이는 동작하지 않음
-
- 테스트하면 비동기로 실행되지 않고 동작이 꼬일 수 있음
예시) UI
더보기
JButton button = new JButton("Order Now");
button.addActionListener(e -> {
Order order = new Order("user1", List.of("item1", "item2"));
order.calculateTotalPrice(); // UI에서 도메인 로직 직접 호출 ❌
JOptionPane.showMessageDialog(null, "Order placed!");
});
- UI 이벤트 안에서 도메인 객체 직접 생성, 로직 실행
- 테스트 어려움
예시) 외부 의존성 통신
더보기
public class WeatherService {
public Weather getCurrentWeather(String city) {
RestTemplate restTemplate = new RestTemplate(); // 외부 통신
String url = "https://api.weather.com/v1/" + city;
return restTemplate.getForObject(url, Weather.class); // 직접 호출 ❌
}
}
- 장애 시 실패 전파가 직접 일어남
험블 객체 패턴
- 복잡한 코드를 쪼개기 위한 패턴
- 테스트가 가능한 부분을 추출
- 결과적으로는 코드는 테스트 가능한 부분을 둘러싼 얇은 험블 래퍼가 됨
- 육각형 아키텍처와 매우 흡사함
- 비즈니스 로직과 오케스트레이션 코드를 분리
예시) 프레임워크 의존성 (비동기)
더보기
public class EmailSender {
public void send(String email, String message) {
// 이메일 발송 (순수 로직. 비동기 X)
}
}
@Service
public class NotificationService {
private final EmailSender emailSender;
public NotificationService(EmailSender emailSender) {
this.emailSender = emailSender;
}
@Async
public void sendEmailAsync(String email, String message) {
emailSender.send(email, message); // 테스트 대상은 이게 아님
}
}
- EmailSender는 단위 테스트 가능 (단순 기능)
- NotificationService는 통합 테스트로 따로 다루기 (언제 어떻게 실행되는지 결정)
예시) UI
더보기
public class OrderService {
public Order createOrder(String userId, List<String> items) {
Order order = new Order(userId, items);
order.calculateTotalPrice();
return order;
}
}
JButton button = new JButton("Order Now");
OrderService orderService = new OrderService();
button.addActionListener(e -> {
Order order = orderService.createOrder("user1", List.of("item1", "item2"));
JOptionPane.showMessageDialog(null, "Order for " + order.getTotalPrice() + " placed!");
});
- OrderService는 로직 테스트
- UI는 입력/출력만
예시) 외부 의존성 통신
더보기
public class WeatherService {
private final WeatherClient weatherClient;
public WeatherService(WeatherClient weatherClient) {
this.weatherClient = weatherClient;
}
public boolean shouldBringUmbrella(String city) {
Weather weather = weatherClient.getCurrentWeather(city);
return weather.isRainy();
}
}
public interface WeatherClient {
Weather getCurrentWeather(String city);
}
public class WeatherClientImpl implements WeatherClient {
private final RestTemplate restTemplate = new RestTemplate();
@Override
public Weather getCurrentWeather(String city) {
String url = "https://api.weather.com/v1/" + city;
return restTemplate.getForObject(url, Weather.class);
}
}
- 테스트에서는 WeatherClient를 Mock으로 대체 가능
'Code > Test' 카테고리의 다른 글
[단위 테스트] 8-1. 통합 테스트를 하는 이유: 통합 테스트 (0) | 2025.02.28 |
---|---|
[단위 테스트] 7-2. 가치 있는 단위 테스트를 위한 리팩터링: 감사 시스템 (0) | 2025.02.28 |
[단위 테스트] 6-2. 단위 테스트 스타일: 함수형 아키텍처 (0) | 2025.01.29 |
[단위 테스트] 6-1. 단위 테스트 스타일: 단위 테스트 스타일 (0) | 2025.01.25 |
[단위 테스트] 5-2. 목과 테스트 취약성: 통신 (0) | 2025.01.24 |