블라디미르 코리코프 님의 "단위 테스트" 책을 정리한 포스팅입니다.
3. 이상적인 테스트를 찾아서
테스트의 가치
- 좋은 단위 테스트의 4대 특성을 곱하여 얻은 결과 (추정치)
- 소수의 가치있는 테스트가 평범한 테스트보다 프로젝트 성장에 훨씬 더 효과적
- 테스트 스위트에 테스트를 계속 둘지 여부를 결정할 수 있음
- 임계치를 충족하는 테스트만 두어, 책임을 적절하게 맡도록 해야 함
이상적인 테스트를 만들 수 있는가?
- 이상적인 테스트는 네 가지 특성 모두에서 최대 점수를 받는 테스트
- 실제로는 세 가지 특성(회귀 방지, 리팩터링 내성, 빠른 피드백)이 서로 충돌함
- 모두를 동시에 만족하는 것은 불가능함
핵심 전략
항목 | 설명 |
하나의 특성을 완전히 버릴 수 없다 |
하나를 버리면 곱셈 규칙(가치의 곱)에 의해 전체 테스트의 유용성이 0에 가까워짐
|
리팩터링 내성은 필수 |
이는 이진 속성이므로, 있거나 없거나이다 → 절대 포기할 수 없음
|
회귀 방지 & 빠른 피드백 |
이 둘은 정도의 차이로 조정 가능 (스펙트럼 상 위치 조정 가능)
|
트레이드 오프
특성 조합 | 설명 |
빠른 피드백을 희생 | 테스트 실행 속도가 느려짐 ex) 엔드 투 엔드(E2E) 테스트 |
회귀 방지를 희생 | 실제 기능 고장을 잘 검출하지 못함 ex) 너무 피상적인 테스트 |
4. 대중적인 테스트 자동화 개념 살펴보기
테스트 피라미드
- 테스트의 종류를 위계 구조로 나눈 모델
- 테스트 유형 간 일정한 비율의 균형을 맞추기 위한 전략 (기반은 넓고, 위로 갈수록 좁아지는 구조)
- 비용과 성능간의 트레이드 오프
구분 | 단위 테스트(Unit) | 통합 테스트(Integration) |
E2E 테스트(End-to-End)
|
범위 | 코드의 최소 단위 (함수, 메서드) | 여러 구성요소 간 상호작용 | 전체 시스템 |
속도 | 매우 빠름 | 중간 | 느림 |
안정성 | 매우 높음 | 보통 | 낮음 |
작성 난이도 | 낮음 | 중간 | 높음 |
비용 | 저렴 | 중간 | 비쌈 |
사용자 관점 | 낮음 | 중간 | 매우 높음 |
예시) 단위 테스트
더보기
@ExtendWith(MockitoExtension.class)
class PostServiceTest {
@Mock
private PostRepository postRepository;
@InjectMocks
private PostService postService;
@Test
void 게시글을_저장한다() {
// Arrange (준비)
Post post = new Post("제목", "내용");
when(postRepository.save(any())).thenReturn(post);
// Act (실행)
Post saved = postService.save(post);
// Assert (검증)
assertEquals("제목", saved.getTitle());
verify(postRepository).save(post);
}
}
- 하나의 클래스/메서드를 외부 의존성 없이 테스트
예시) 통합 테스트
더보기
@SpringBootTest
@AutoConfigureTestDatabase
@Transactional
class PostServiceIntegrationTest {
@Autowired
private PostService postService;
@Autowired
private PostRepository postRepository;
@Test
void 게시글을_저장하고_조회한다() {
// Arrange
Post post = new Post("제목", "내용");
// Act
postService.save(post);
Post found = postRepository.findById(post.getId()).orElseThrow();
// Assert
assertEquals("제목", found.getTitle());
}
}
- 컴포넌트 간 연동이 제대로 이루어지는지 확인
- 여러 컴포넌트를 함께 묶어 실제 환경과 유사하게 테스트
예시) E2E 테스트
더보기
@SpringBootTest
@AutoConfigureMockMvc
class PostControllerE2ETest {
@Autowired
private MockMvc mockMvc;
@Test
void 게시글을_등록하고_조회할_수_있다() throws Exception {
// 게시글 등록
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "제목",
"content": "내용"
}
"""))
.andExpect(status().isCreated());
// 게시글 목록 조회
mockMvc.perform(get("/posts"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].title").value("제목"));
}
}
- 전체 시스템을 사용자의 관점에서 테스트 (UI/API 포함)
- 실제 사용자 흐름이 잘 작동하는지 확인
블랙박스 테스트와 화이트박스 테스트 간의 선택
항목 | 블랙박스 테스트 | 화이트박스 테스트 |
작성자 | 테스터, QA 엔지니어 |
개발자
|
접근 방식 | 시스템의 내부 구조를 모른 채 테스트 |
내부 코드와 로직을 분석하여 테스트
|
기준 | 명세서, 요구사항 기반 |
소스코드 구조 기반
|
초점 | 기능 검증 |
구현 검증
|
리팩토링 내성 | 강함 (내부 코드 변경이 있어도 테스트 영향 적음) |
약함 (내부 코드 변경에 따라 테스트가 쉽게 깨짐)
|
회귀 방지 효과 | 약함 |
강함 (코드 변경에 따른 오류 탐지에 유리)
|
사용 시점 | 외부 인터페이스를 기준으로 테스트할 때 |
코드 레벨 검토나 로직 검증이 필요할 때
|
사례 | UI 테스트, API 응답 검증, 기능 요구사항 기반 테스트 |
조건문 분기 커버리지, 루프 검사, 경계값 테스트 등
|
예시) 블랙박스 테스트
더보기
@SpringBootTest
class UserServiceBlackBoxTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void 사용자는_정상적으로_회원가입할_수_있다() {
// given
String email = "test@example.com";
String password = "secure123";
// when
userService.register(email, password);
// then
User savedUser = userRepository.findByEmail(email).orElseThrow();
assertEquals(email, savedUser.getEmail());
assertNotEquals(password, savedUser.getPassword()); // 암호화되었는지 확인
}
}
예시) 화이트박스 테스트
더보기
class PasswordValidator {
public boolean isValid(String password) {
return password.length() >= 8 &&
password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*") &&
password.matches(".*[0-9].*");
}
}
class PasswordValidatorWhiteBoxTest {
private final PasswordValidator validator = new PasswordValidator();
@Test
void 길이_8미만이면_false() {
assertFalse(validator.isValid("Abc123"));
}
@Test
void 대문자가_없으면_false() {
assertFalse(validator.isValid("abcdefg1"));
}
@Test
void 숫자가_없으면_false() {
assertFalse(validator.isValid("Abcdefgh"));
}
@Test
void 모든_조건을_만족하면_true() {
assertTrue(validator.isValid("Abcdefg1"));
}
}
'Code > Test' 카테고리의 다른 글
[단위 테스트] 5-2. 목과 테스트 취약성: 통신 (1) | 2025.01.24 |
---|---|
[단위 테스트] 5-1. 목과 테스트 취약성: 목 (0) | 2025.01.24 |
[단위 테스트] 4-1. 좋은 단위 테스트의 4대 요소: 4대 요소 (0) | 2025.01.19 |
[단위 테스트] 3. 단위 테스트 구조 (0) | 2025.01.07 |
[단위 테스트] 2-2. 단위 테스트란 무엇인가: 런던파와 고전파 (1) | 2025.01.07 |