반응형
단위 테스트란?
- 가장 작은 단위의 코드를 테스트
- 보통 하나의 메서드나 클래스를 테스트
- 외부 의존성을 Mock으로 대체
- 빠르고 독립적
- TDD의 기본
통합 테스트란?
- 여러 컴포넌트가 함께 동작하는지 테스트
- 실제 DB, API 등 외부 시스템과 연동
- 단위 테스트보다 느림
- 실제 환경과 비슷한 조건에서 테스트
주요 차이점
구분 단위 테스트 통합 테스트
범위 | 개별 메서드/클래스 | 여러 컴포넌트 |
속도 | 빠름 | 느림 |
외부 의존성 | Mock 사용 | 실제 시스템 사용 |
격리성 | 완전 격리 | 부분 격리 |
복잡도 | 단순 | 복잡 |
Java 단위 테스트 예제
테스트 대상 클래스
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("0으로 나눌 수 없습니다");
}
return a / b;
}
}
JUnit5 단위 테스트
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator = new Calculator();
@Test
@DisplayName("두 수를 더하면 올바른 결과가 나온다")
void testAdd() {
// Given
int a = 5;
int b = 3;
// When
int result = calculator.add(a, b);
// Then
assertEquals(8, result);
}
@Test
@DisplayName("0으로 나누면 예외가 발생한다")
void testDivideByZero() {
// Given
int a = 10;
int b = 0;
// When & Then
assertThrows(IllegalArgumentException.class,
() -> calculator.divide(a, b));
}
}
Java 통합 테스트 예제
테스트 대상 서비스 클래스
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(String name, String email) {
User user = new User(name, email);
return userRepository.save(user);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
Spring Boot 통합 테스트
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
@Transactional
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("사용자 생성 후 DB에서 조회 가능하다")
void testCreateAndRetrieveUser() {
// Given
String name = "김개발";
String email = "dev@example.com";
// When
User createdUser = userService.createUser(name, email);
List<User> allUsers = userService.getAllUsers();
// Then
assertNotNull(createdUser.getId());
assertEquals(name, createdUser.getName());
assertEquals(email, createdUser.getEmail());
assertTrue(allUsers.contains(createdUser));
}
}
Mock을 사용한 단위 테스트
@ExtendWith(MockitoExtension.class)
class UserServiceUnitTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
@DisplayName("사용자 생성 시 Repository의 save 메서드가 호출된다")
void testCreateUser() {
// Given
String name = "김개발";
String email = "dev@example.com";
User expectedUser = new User(name, email);
expectedUser.setId(1L);
when(userRepository.save(any(User.class)))
.thenReturn(expectedUser);
// When
User result = userService.createUser(name, email);
// Then
assertEquals(expectedUser.getId(), result.getId());
assertEquals(name, result.getName());
assertEquals(email, result.getEmail());
verify(userRepository, times(1)).save(any(User.class));
}
}
언제 어떤 테스트를 사용할까?
단위 테스트 사용 시기
- 비즈니스 로직 검증
- 예외 상황 테스트
- 빠른 피드백이 필요한 경우
- TDD 개발 시
통합 테스트 사용 시기
- 여러 레이어 간 상호작용 확인
- DB 연동 로직 검증
- API 엔드투엔드 테스트
- 실제 환경과 유사한 테스트 필요 시
단위 테스트 작성 팁
- AAA 패턴 사용 (Arrange, Act, Assert)
- 테스트 메서드 이름을 명확하게
- 하나의 테스트는 하나의 검증만
- 경계값 테스트 필수
통합 테스트 작성 팁
- @Transactional로 롤백 처리
- 테스트용 설정 파일 분리
- 테스트 데이터 초기화 주의
- 실행 시간 고려해서 필요한 것만
테스트 피라미드
/\
/ \ E2E Tests (적음)
/____\
/ \
/ \ Integration Tests (보통)
/__________\
/ \
Unit Tests (많음)
- 단위 테스트: 70%
- 통합 테스트: 20%
- E2E 테스트: 10%
단위 테스트와 통합 테스트는 각각의 목적이 다르다.
둘 다 중요하지만 단위 테스트를 먼저 탄탄히 하고, 필요한 부분에 통합 테스트를 추가하는 것이 좋다.
빠른 피드백과 안정적인 리팩토링을 위해서는 단위 테스트가 핵심이고,
실제 동작 확인을 위해서는 통합 테스트가 필수다.
728x90
반응형
'코딩:개발일지' 카테고리의 다른 글
코드 리뷰에서 자주 지적받는 실수는 무엇일까 (0) | 2025.05.31 |
---|---|
테스트 커버리지는 얼마나 확보해야 할까? (0) | 2025.05.30 |
Redis는 어떤 상황에서 사용하는 게 좋을까? (0) | 2025.05.27 |
JWT vs OAuth2 정리 - 백엔드 개발자가 알아야 할 개념 (1) | 2025.05.25 |
ORM(Object-Relational Mapping)은 꼭 써야 하나요? (0) | 2025.05.21 |