IT Professional Engineering/SW
테스트 스텁(Test Stub): 모듈 간 의존성 문제 해결을 위한 테스트 더블 기법
GilliLab IT
2025. 3. 27. 22:45
728x90
반응형
테스트 스텁(Test Stub): 모듈 간 의존성 문제 해결을 위한 테스트 더블 기법
- 테스트 스텁의 개념
- 테스트 스텁의 필요성
- 테스트 스텁과 다른 테스트 더블의 비교
- 테스트 스텁 구현 방법
- 실제 사례: 데이터베이스 연동 테스트
- 테스트 스텁 설계 원칙
- 테스트 스텁의 장단점
- 테스트 스텁의 활용 시나리오
- 테스트 스텁 구현 시 모범 사례
- 마이크로서비스 환경에서의 테스트 스텁 활용
- 결론
- Keywords
테스트 스텁의 개념
- 정의: 테스트 대상 모듈이 호출하는 다른 소프트웨어 구성 요소(모듈, 변수, 객체)를 일시적으로 대체하는 소프트웨어 구성 요소
- 목적: 복잡한 의존성을 단순화하여 격리된 환경에서 테스트 진행 가능
- 테스트 더블(Test Double)의 한 유형: 테스트 더블은 실제 객체 대신 테스트에서 사용되는 모든 종류의 가짜 객체를 포괄하는 용어
- 사용 시점: 의존하는 컴포넌트가 아직 개발되지 않았거나, 실행하기 어려운 경우, 실행 시간이 오래 걸리는 경우에 활용
테스트 스텁의 필요성
- 모듈 간 의존성 문제 해결: 모듈 A가 모듈 B에 의존할 때, B가 없어도 A를 테스트 가능
- 테스트 격리(Isolation): 테스트 대상 코드만 검증 가능한 환경 조성
- 제어 불가능한 요소 제어: 네트워크, 데이터베이스, 외부 API 등 통제하기 어려운 요소를 제어 가능한 형태로 대체
- 예측 가능한 테스트 결과: 일관된 테스트 결과를 얻을 수 있어 테스트 신뢰성 향상
- 테스트 속도 개선: 복잡한 연산이나 I/O 작업을 단순화하여 테스트 실행 속도 향상
테스트 스텁과 다른 테스트 더블의 비교
graph TD
A[테스트 더블] --> B[테스트 스텁]
A --> C[테스트 스파이]
A --> D[테스트 페이크]
A --> E[테스트 목]
A --> F[테스트 더미]
B --> B1[입력에 대한 미리 정의된 응답 반환]
C --> C1[호출 여부, 방법 기록]
D --> D1[실제 구현의 간소화된 버전]
E --> E1[예상되는 호출 검증]
F --> F1[전달만 되고 사용되지 않는 객체]
- 테스트 스텁: 호출에 대한 하드코딩된 응답 제공, 행동 검증 없음
- 테스트 목(Mock): 예상되는 호출을 검증하고 미리 프로그래밍된 응답 제공
- 테스트 스파이(Spy): 실제 객체를 사용하되 호출 정보를 기록
- 테스트 페이크(Fake): 실제 구현의 경량화된 버전(예: 인메모리 DB)
- 테스트 더미(Dummy): 전달용으로만 사용되고 실제로는 사용되지 않는 객체
테스트 스텁 구현 방법
1. 수동 구현 방식
// 원래 의존하는 실제 서비스 인터페이스
public interface PaymentGateway {
boolean processPayment(double amount);
}
// 테스트를 위한 스텁 구현
public class PaymentGatewayStub implements PaymentGateway {
@Override
public boolean processPayment(double amount) {
// 항상 성공 반환 (실제 지불 처리 로직 대체)
return true;
}
}
// 테스트 코드
@Test
public void testOrderProcessWithPayment() {
// 스텁 객체 생성
PaymentGateway stubGateway = new PaymentGatewayStub();
// 테스트 대상 객체에 스텁 주입
OrderProcessor processor = new OrderProcessor(stubGateway);
// 테스트 실행
boolean result = processor.processOrder(new Order(100.0));
// 결과 검증
assertTrue(result);
}
2. 모킹 프레임워크 활용 (예: Mockito)
@Test
public void testOrderProcessWithMockito() {
// Mockito를 사용한 스텁 생성
PaymentGateway stubGateway = Mockito.mock(PaymentGateway.class);
// 스텁 동작 정의
Mockito.when(stubGateway.processPayment(Mockito.anyDouble())).thenReturn(true);
// 테스트 대상 객체에 스텁 주입
OrderProcessor processor = new OrderProcessor(stubGateway);
// 테스트 실행
boolean result = processor.processOrder(new Order(100.0));
// 결과 검증
assertTrue(result);
}
실제 사례: 데이터베이스 연동 테스트
문제 상황
- 사용자 서비스 모듈이 데이터베이스 접근 계층에 의존
- 테스트 시 실제 DB 연결은 느리고 환경 의존적
스텁 적용 전 코드
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public User findUserById(long id) {
return repository.findById(id);
}
public boolean isUserPremium(long id) {
User user = repository.findById(id);
return user != null && user.isPremium();
}
}
// 실제 DB에 접근하는 리포지토리
public class UserRepositoryImpl implements UserRepository {
@Override
public User findById(long id) {
// 실제 데이터베이스에서 사용자 조회
Connection conn = DatabaseConnection.getConnection();
// SQL 실행 및 결과 매핑...
return user;
}
}
스텁 적용 후 테스트 코드
@Test
public void testIsUserPremium_WithPremiumUser() {
// 테스트용 스텁 생성
UserRepository stubRepository = new UserRepositoryStub();
// 서비스에 스텁 주입
UserService service = new UserService(stubRepository);
// 테스트 실행
boolean isPremium = service.isUserPremium(1L);
// 결과 검증
assertTrue(isPremium);
}
// 스텁 구현
class UserRepositoryStub implements UserRepository {
@Override
public User findById(long id) {
// 항상 프리미엄 사용자 반환
User stubUser = new User(id, "Test User", true);
return stubUser;
}
}
테스트 스텁 설계 원칙
- 단순성: 가능한 간단하게 설계하여 테스트 자체의 복잡도 증가 방지
- 일관성: 동일한 입력에 항상 동일한 결과 반환
- 관련성: 테스트 목적과 관련된 응답만 구현
- 격리성: 실제 의존성으로부터 테스트 대상 완전 격리
- 가독성: 다른 개발자가 이해하기 쉽게 작성
테스트 스텁의 장단점
장점
- 테스트 속도 향상: 복잡한 처리를 단순화하여 테스트 속도 개선
- 결정적(Deterministic) 테스트: 일관된 결과 보장으로 테스트 안정성 증가
- 의존성 단순화: 복잡한 외부 의존성 없이 테스트 가능
- 에지 케이스 테스트: 실제 환경에서 재현하기 어려운 상황 시뮬레이션 가능
- 병렬 개발: 의존하는 컴포넌트가 완성되기 전에도 개발 및 테스트 진행 가능
단점
- 실제 환경 차이: 스텁은 실제 구현체와 동작이 다를 수 있음
- 유지보수 부담: 인터페이스 변경 시 스텁도 함께 수정 필요
- 테스트 복잡성: 과도한 스텁 사용은 테스트 코드 복잡성 증가 초래
- 통합 이슈 발견 어려움: 컴포넌트 간 실제 상호작용 문제 발견 어려움
테스트 스텁의 활용 시나리오
1. 외부 API 의존성 처리
- 문제: 결제 처리 시 외부 결제 게이트웨이 API 호출 필요
- 해결: 결제 API 스텁을 만들어 다양한 응답 시나리오 테스트
// 결제 API 스텁
public class PaymentGatewayStub implements PaymentGateway {
private boolean shouldSucceed;
public PaymentGatewayStub(boolean shouldSucceed) {
this.shouldSucceed = shouldSucceed;
}
@Override
public PaymentResult processPayment(PaymentRequest request) {
if (shouldSucceed) {
return new PaymentResult(true, "Payment successful", "TX123456");
} else {
return new PaymentResult(false, "Insufficient funds", null);
}
}
}
2. 시간 의존적 코드 테스트
- 문제: 특정 시간에 따라 다른 결과를 반환하는 로직
- 해결: 시간 제공자 인터페이스를 만들고 스텁으로 대체
public interface TimeProvider {
LocalDateTime getCurrentTime();
}
// 테스트용 시간 제공자 스텁
public class FixedTimeProvider implements TimeProvider {
private final LocalDateTime fixedTime;
public FixedTimeProvider(LocalDateTime fixedTime) {
this.fixedTime = fixedTime;
}
@Override
public LocalDateTime getCurrentTime() {
return fixedTime;
}
}
// 테스트 코드
@Test
public void testBusinessHoursCheck() {
// 업무 시간 내 고정 시간 스텁
TimeProvider businessHoursStub = new FixedTimeProvider(
LocalDateTime.of(2023, 5, 10, 14, 0)); // 오후 2시
BusinessService service = new BusinessService(businessHoursStub);
assertTrue(service.isDuringBusinessHours());
// 업무 시간 외 고정 시간 스텁
TimeProvider afterHoursStub = new FixedTimeProvider(
LocalDateTime.of(2023, 5, 10, 22, 0)); // 오후 10시
service = new BusinessService(afterHoursStub);
assertFalse(service.isDuringBusinessHours());
}
테스트 스텁 구현 시 모범 사례
- 인터페이스 기반 설계: 의존성을 인터페이스로 추상화하여 스텁 교체 용이성 확보
- 의존성 주입 활용: 생성자/세터 주입으로 테스트 시 스텁 교체 용이성 제공
- 경계값 테스트: 정상 케이스뿐만 아니라 예외 상황도 스텁으로 시뮬레이션
- 스텁 재사용: 공통 스텁 라이브러리 구축으로 중복 코드 감소
- 상태 확인: 스텁 사용 후 검증은 상태(state) 기반으로 수행
마이크로서비스 환경에서의 테스트 스텁 활용
graph LR
A[주문 서비스] --> B[결제 서비스]
A --> C[재고 서비스]
A --> D[배송 서비스]
subgraph "테스트 환경"
A --> B1[결제 서비스 스텁]
A --> C1[재고 서비스 스텁]
A --> D1[배송 서비스 스텁]
end
- 서비스 계약 테스트: 마이크로서비스 간 API 계약 준수 확인
- 장애 시나리오 테스트: 다른 서비스 장애 상황 시뮬레이션
- 성능 테스트: 의존 서비스 지연 없이 부하 테스트 수행
- 개발 생산성: 다른 팀의 서비스 완성 전에도 개발 진행 가능
결론
- 테스트 스텁은 복잡한 의존성을 단순화하여 테스트 환경을 격리하고, 예측 가능한 결과를 제공함으로써 테스트 신뢰성과 효율성을 향상시킨다.
- 이를 통해 개발자는 실제 의존성에 구애받지 않고 독립적으로 테스트를 수행할 수 있다.
- 테스트 스텁은 특히 외부 시스템과의 통합 테스트에서 유용하며, 개발 초기 단계에서 빠른 피드백을 제공한다.
- 하지만 스텁의 구현이 실제 동작과 다를 경우 테스트 결과의 신뢰성이 떨어질 수 있으므로, 적절한 설계와 관리가 필요하다.
Keywords
Test Double, Stub, Unit Testing, 모듈 의존성, 테스트 대역, 격리 테스트, 소프트웨어 테스팅, 테스트 자동화, Mock Object
728x90
반응형