항목 | 설명 |
JUnit | 자바(Java) 언어용 단위 테스트(Unit Test) 프레임워크. 테스트 클래스를 만들고, 메서드 단위로 기능을 검증할 수 있게 해주는 도구다. 대표적인 테스트 어노테이션으로 @Test, @BeforeEach, @AfterEach 등이 있다. |
테스트 코드 | 실제 애플리케이션 코드가 기대한 대로 동작하는지를 검증하는 코드. 주로 자동화된 형태로 작성되며, 수동 테스트 없이 반복 실행 가능하다. 테스트 코드는 버그 방지, 리팩토링 안전성 확보, 신뢰성 있는 개발에 도움을 준다. |
추가 설명:
- JUnit의 장점
- 자동화된 테스트를 통해 빠르고 반복 가능한 검증 가능
- CI/CD 파이프라인에서 핵심 역할
- 다양한 assertion 메서드(assertEquals, assertTrue, 등)를 통해 로직 검증
- 테스트 코드의 종류
- 단위 테스트 (Unit Test): 메서드/클래스 단위로 작동 검증
- 통합 테스트 (Integration Test): 여러 컴포넌트 간의 연동 검증
- 인수 테스트 (Acceptance Test): 전체 기능이 요구사항을 만족하는지 검증
✅ 테스트 종류별 비교
구분 | 단위 테스트 (Unit Test) | 통합 테스트 (Integration Test) | 인수 테스트 (Acceptance Test) |
목적 | 개별 기능이 정확히 동작하는지 확인 | 모듈 간 연결 및 협업이 올바르게 동작하는지 확인 | 전체 시스템이 사용자 요구를 충족하는지 확인 |
범위 | 메서드, 클래스 단위 | 여러 모듈 간 상호작용 | 사용자 시나리오 전체 |
작성 대상 | 개발자 | 개발자/테스터 | 테스터, QA 또는 사용자 관점 |
실행 속도 | 매우 빠름 (ms 단위) | 중간 | 느림 (초~분 단위) |
의존성 | 없음 또는 Mock 객체로 대체 | 실제 DB, API, 네트워크 등 사용 | 전체 시스템 구성 필요 |
예시 | 회원가입() 함수 하나만 테스트 | 회원가입 → 이메일 인증 → 로그인 연결 테스트 | “신규 유저가 회원가입 후 로그인까지 완료하는 시나리오” 전체 테스트 |
🧪 예시 설명
1. 단위 테스트
- 대상: UserService.createUser()
- 설명: 이 메서드 하나만 테스트하며, DB나 외부 서비스는 Mock 처리함
- 예시 코드 (JUnit + Mockito 사용):
@Test
void createUser_shouldReturnSavedUser() {
when(userRepository.save(any())).thenReturn(new User("test@example.com"));
User user = userService.createUser("test@example.com");
assertEquals("test@example.com", user.getEmail());
}
2. 통합 테스트
- 대상: UserService + UserRepository + 실제 DB
- 설명: 여러 구성 요소가 함께 동작하는지 검증. 보통 @SpringBootTest 사용
- 예시 코드:
@SpringBootTest
@Transactional
class UserIntegrationTest {
@Autowired
private UserService userService;
@Test
void userCreationAndRetrieval() {
userService.createUser("hello@world.com");
User found = userService.findByEmail("hello@world.com");
assertNotNull(found);
}
}
3. 인수 테스트
- 대상: 실제 사용자 시나리오 전체
- 도구: Postman, Selenium, RestAssured, Cypress 등
- 예시 시나리오: “신규 사용자가 회원가입 후 로그인하면 JWT 토큰이 발급된다.”
- 설명: 진짜 사용자처럼 요청 → 응답 흐름을 테스트함
🔄 관계 요약
- 단위 테스트: 내부 로직 검증 (기초)
- 통합 테스트: 구성 요소 연동 확인 (구조 점검)
- 인수 테스트: 외부 사용자 관점 검증 (시나리오 최종 확인)
1. 🔁 Mock 이란?
항목 | 설명 |
정의 | 실제 객체의 동작을 흉내 내는 가짜 객체. 테스트 대상 코드가 외부 시스템(DB, API, etc.)에 의존할 경우 그 외부 요소를 대체하여 테스트가 독립적이고 빠르게 수행되도록 도와줌. |
목적 | - 테스트 속도 향상 - 외부 의존 제거 - 실패 요인 단순화 - 경계 조건 테스트 용이 |
예시 | 예를 들어, 실제 DB에 연결하지 않고도 UserRepository의 findById()를 실행한 것처럼 동작시킬 수 있음. |
2. 🛠 Mockito란?
항목 | 설명 |
정의 | 자바에서 Mock 객체를 쉽게 만들 수 있도록 도와주는 대표적인 Mocking 프레임워크. |
주요 기능 | - @Mock 으로 객체 생성 - when().thenReturn() 으로 행동 지정 - verify() 로 호출 여부 검증 - @InjectMocks 로 테스트 대상 클래스에 Mock 주입 |
필요 라이브러리 | testImplementation 'org.mockito:mockito-core:4.x' 또는 mockito-inline, mockito-junit-jupiter 등 |
✅ Mockito 사용 예제
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void 사용자_생성_성공() {
// Given
User user = new User("test@example.com");
when(userRepository.save(any())).thenReturn(user);
// When
User result = userService.createUser("test@example.com");
// Then
assertEquals("test@example.com", result.getEmail());
verify(userRepository).save(any());
}
}
3. 📐 Given-When-Then 구조란?
🧩 정의
BDD(Behavior Driven Development, 행위 주도 개발) 스타일의 테스트 작성 방식이며, 테스트 코드를 세 가지 단계로 구분해서 가독성과 의도를 높이는 구조다.
단계 | 역할 | 예시 설명 |
Given | 테스트에 필요한 사전 조건/입력 설정 | 사용자가 존재하고, 저장소가 정상 응답한다고 가정 |
When | 행동 또는 메서드 호출 수행 | userService.createUser() 호출 |
Then | 결과 검증 (assert 등) | email이 맞는지 확인, save가 호출됐는지 확인 |
📌 GWT 구조로 다시 본 테스트 코드
@Test
void 사용자_생성_성공() {
// Given (상황 설정)
User user = new User("test@example.com");
when(userRepository.save(any())).thenReturn(user);
// When (행동 실행)
User result = userService.createUser("test@example.com");
// Then (결과 검증)
assertEquals("test@example.com", result.getEmail());
verify(userRepository).save(any());
}
4. 🔄 정리
항목 | 요약 |
Mock | 가짜 객체로, 외부 의존을 끊고 테스트 독립성 확보 |
Mockito | Mock 객체를 쉽게 만들고 동작을 정의할 수 있는 프레임워크 |
Given-When-Then | 테스트 코드의 흐름을 명확히 하기 위한 구조적 작성 방식 (BDD) |
✅ 1. Mock vs Mockito: 언제 어떤 걸 쓰는가?
항목 | Mock | Mockito |
개념 | “가짜 객체” 그 자체 | “Mock 객체를 쉽게 만드는 도구(프레임워크)” |
언제 사용하는가 | 외부 의존(DB, API 등)을 대체할 가짜 객체가 필요할 때 | Mock 객체를 만들고 조작해야 할 때 (즉, Mock을 실제로 사용할 때) |
예시 | - UserRepository를 가짜로 만들어야 함 (Mock 필요) - DB 접근 없이 동작 검증하고 싶음 |
- 그 Mock이 어떻게 동작해야 할지 설정 (when, thenReturn) - 그 Mock이 제대로 호출됐는지 검증 (verify) |
즉, Mock은 ‘무대 소품’, Mockito는 그 소품을 ’연출하는 도구’라고 생각하면 돼.
✅ 2. Mockito의 주요 함수 설명 (when, thenReturn, verify 등)
🔹 ① when(...).thenReturn(...)
- 의미: 특정 메서드가 호출되었을 때 어떤 값을 반환할지 설정함.
- 사용 위치: Given 영역
- 예시:
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
🔹 ② when(...).thenThrow(...)
- 의미: 예외를 발생시키도록 설정
- 예시:
when(userRepository.findById(-1L)).thenThrow(new IllegalArgumentException("Invalid ID"));
🔹 ③ verify(...)
- 의미: 특정 Mock 메서드가 실제로 호출되었는지 검증
- 사용 위치: Then 영역
- 옵션:
- verify(mock).method() → 정확히 한 번 호출
- verify(mock, times(2)) → 두 번 호출
- verify(mock, never()) → 호출되지 않음
- 예시:
verify(userRepository).save(any());
verify(userRepository, times(1)).save(any());
verify(userRepository, never()).delete(any());
🔹 ④ any(), eq(), argThat() 등 (매개변수 지정)
함수 | 설명 |
any() | 어떤 값이든 허용 |
anyString(), anyLong() 등 | 특정 타입의 어떤 값이든 허용 |
eq(x) | 정확히 x와 같은 값이어야 함 |
argThat(predicate) | 조건을 만족하는 값만 허용 |
when(userRepository.findByEmail(anyString())).thenReturn(user);
✅ Mockito 인자 매처 종류와 사용법
매처 함수 | 설명 | 사용 예시 |
any() | 어떤 타입이든 허용 | any(User.class) |
anyString() | 어떤 문자열이든 허용 | anyString() |
anyInt() | 어떤 정수든 허용 | anyInt() |
eq(value) | 정확히 value와 같은 값이어야 함 | eq("abc") |
argThat(predicate) | 조건을 만족하는 값만 허용 | argThat(x -> x.length() > 5) |
🧪 실전 예제
가정: UserService.createUser(String email) 메서드를 테스트한다고 할 때.
@Test
void 사용자_생성_성공() {
// Given
when(userRepository.save(any())).thenReturn(new User("test@example.com"));
// When
User result = userService.createUser("test@example.com");
// Then
verify(userRepository).save(any());
assertEquals("test@example.com", result.getEmail());
}
① any() – 어떤 값이든 OK
when(userRepository.save(any(User.class)))
.thenReturn(new User("abc@def.com"));
verify(userRepository).save(any()); // 타입 생략 가능 (Generic 자동 추론)
② anyString() / anyInt() / anyLong() 등
when(userRepository.findByEmail(anyString()))
.thenReturn(Optional.of(new User("abc@def.com")));
verify(userRepository).findByEmail(anyString());
③ eq("정확한 값") – 딱 그 값만!
when(userRepository.findByEmail(eq("hello@world.com")))
.thenReturn(Optional.of(new User("hello@world.com")));
⚠️ 주의: eq()와 any() 같은 matcher를 혼합해서 쓸 땐 모든 인자에 매처를 써야 한다.
// 잘못된 예 (하나는 eq, 하나는 실제 값)
when(service.doSomething(eq("hi"), 123)); // ❌ 오류 발생
// 올바른 예
when(service.doSomething(eq("hi"), eq(123)));
④ argThat(predicate) – 커스텀 조건!
when(userRepository.save(argThat(user -> user.getEmail().endsWith("@gmail.com"))))
.thenReturn(new User("filtered@gmail.com"));
🔄 종합 예제
verify(userRepository).save(any(User.class));
verify(userRepository).findByEmail(eq("abc@def.com"));
verify(userRepository).save(argThat(user -> user.getEmail().contains("naver")));
💡 추가 Tip: ArgumentCaptor (캡처해서 직접 확인하기)
@Captor
private ArgumentCaptor<User> userCaptor;
@Test
void 캡처된_객체_검증() {
userService.createUser("test@abc.com");
verify(userRepository).save(userCaptor.capture());
assertEquals("test@abc.com", userCaptor.getValue().getEmail());
}
✅ 요약표
매처 | 설명 | 대표 예시 |
any() | 어떤 객체든 OK | any(User.class) |
anyString() | 어떤 문자열 | anyString() |
eq(x) | 정확히 x 값 | eq("email") |
argThat() | 조건을 만족하는 값 | argThat(x -> x > 5) |
✅ 3. 예제 코드 (전체 흐름)
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void 이메일로_유저_조회_성공() {
// Given
User user = new User("test@example.com");
when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(user));
// When
User result = userService.getUserByEmail("test@example.com");
// Then
assertEquals("test@example.com", result.getEmail());
verify(userRepository).findByEmail("test@example.com");
}
}
✅ 결론 요약
상황 | 사용하는 것 |
외부 의존을 대체하고 싶다 | Mock 객체 필요 → @Mock |
Mock 객체에 동작을 정의하고 싶다 | when(...).thenReturn(...) |
Mock이 어떻게 불렸는지 확인하고 싶다 | verify(...) |
다양한 인자 처리하고 싶다 | any(), eq(), argThat() 등 |
✅ 1. @MockBean이란?
항목 | 설명 |
정의 | Spring Context에 있는 Bean을 Mock 객체로 교체해주는 애너테이션 |
적용 대상 | 보통 @SpringBootTest, @WebMvcTest와 함께 사용 |
사용 목적 | 실제 Bean을 교체해서 외부 의존을 차단하고, 원하는 방식으로 제어 가능 |
동작 | 기존 실제 빈을 덮어써서 테스트 대상만 집중할 수 있게 함 |
🔸 예시
@SpringBootTest
class UserServiceTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService;
@Test
void 유저조회() {
when(userRepository.findByEmail("test@example.com"))
.thenReturn(Optional.of(new User("test@example.com")));
User result = userService.getUserByEmail("test@example.com");
assertEquals("test@example.com", result.getEmail());
}
}
🧠 핵심: 기존에 스프링이 만든 Bean을 Mock으로 바꿔치기 한다는 점이 포인트.
✅ 2. @SpringBootTest vs @WebMvcTest
항목 | @SpringBootTest | @WebMvcTest |
로딩 범위 | 전체 애플리케이션 Context | @Controller, @RestController 관련 Bean만 로드 |
속도 | 느림 (많은 Bean 로딩) | 빠름 (Web Layer만 로드) |
사용 목적 | 통합 테스트 (Service, Repository 포함) | Web Layer 단위 테스트 |
대표 사용 대상 | 전체 흐름 검증 (DB 포함) | 컨트롤러 테스트 (요청/응답 확인) |
Repository/Service 사용 | 자동 주입됨 (실제 Bean) | 자동 주입 안 됨 → @MockBean 필요 |
🔸 예시: @WebMvcTest
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void 유저조회API_성공() throws Exception {
when(userService.getUserByEmail("test@example.com"))
.thenReturn(new User("test@example.com"));
mockMvc.perform(get("/users/email")
.param("email", "test@example.com"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("test@example.com"));
}
}
✅ 정리표
구분 | @SpringBootTest | @WebMvcTest | @MockBean |
테스트 범위 | 전체 앱 통합 | Web 계층 (Controller) | 실제 Bean 대신 Mock |
속도 | 느림 | 빠름 | - |
Bean 주입 | 실제 Bean 사용 | Controller만 주입됨 | 필요할 때 사용 |
주요 용도 | 서비스/DB 포함 전체 흐름 | API 요청/응답 테스트 | 외부 의존 차단 |
✅ 언제 쓰나?
- @SpringBootTest: 진짜 DB, 서비스, 리포지토리까지 포함해서 테스트하고 싶을 때
- @WebMvcTest: 컨트롤러 단독 테스트. 응답 JSON, HTTP 상태 등 검증할 때
- @MockBean: @WebMvcTest나 @SpringBootTest에서 일부 Bean을 가짜로 바꿔서 제어하고 싶을 때
✅ 핵심 요약
어노테이션 | 역할 |
@Mock | 가짜 객체를 만든다 (예: UserRepository) |
@InjectMocks | 이 객체 안에 있는 @Mock 객체들을 자동으로 주입해서 테스트 대상 객체를 완성해준다 (예: UserService) |
📦 비유: 레고 조립
- UserService는 여러 부품(= 의존성)을 조립해야 하는 레고 본체
- UserRepository는 그 레고를 만들기 위한 부품
- @Mock은 가짜 부품을 만든다.
- @InjectMocks은 부품을 끼워서 완성품을 만들어주는 작업자
🔧 작동 구조
@Mock
private UserRepository userRepository;
- 여기서 userRepository는 진짜 객체가 아니라, Mockito가 만든 “가짜 객체 (Mock)“임
- 예: when(userRepository.save(...)).thenReturn(...) 이런 걸로 동작을 지정함
@InjectMocks
private UserService userService;
- 여기서 Mockito는 내부적으로 new UserService(userRepository) 와 비슷한 일을 함
- 즉, 위에서 만든 @Mock 객체를 UserService의 생성자나 필드에 자동으로 주입해서 테스트 대상 객체를 만들어줌
🔁 그림으로 보면 이렇게
[Mock 객체 생성]
┌───────────────────────┐
│ userRepository (Mock) │
└───────────────────────┘
[InjectMocks]
┌─────────────────────────────┐
│ UserService │
│ ┌──────────────────┐ │
│ │ userRepository <--(주입됨) │
│ └──────────────────┘ │
└─────────────────────────────┘
✅ 왜 이렇게 쓰는가?
테스트 환경에서는 우리가 직접 new UserService(mockRepository) 같은 코드를 작성하지 않아도 되도록,Mockito가 @InjectMocks로 자동 주입해주는 것.
즉,
- @Mock → 의존성 가짜로 생성
- @InjectMocks → 테스트 대상 객체 생성 + 위 가짜 의존성 주입
🧠 한 줄 요약
@Mock은 부품을 만들고, @InjectMocks는 그 부품으로 조립된 본체(테스트 대상)를 만들어준다.
✅ 1. assertEquals, assertTrue 등 – JUnit 기본 어설션
메서드 | 설명 |
assertEquals(expected, actual) | 두 값이 같은지 확인 |
assertNotEquals(a, b) | 두 값이 서로 다른지 확인 |
assertTrue(condition) | 조건이 참인지 확인 |
assertFalse(condition) | 조건이 거짓인지 확인 |
assertNull(obj) | 객체가 null인지 확인 |
assertNotNull(obj) | 객체가 null이 아닌지 확인 |
assertThrows() | 예외가 발생하는지 확인 |
📦 출처: org.junit.jupiter.api.Assertions (JUnit 5)
import static org.junit.jupiter.api.Assertions.*;
@Test
void exampleTest() {
assertEquals(2, 1 + 1);
assertTrue("abc".startsWith("a"));
assertThrows(IllegalArgumentException.class, () -> someMethod(-1));
}
✅ 2. assertThat – 더 유연하고 표현력 있는 검증
특징 | 내용 |
출처 | org.assertj.core.api.Assertions.* (AssertJ 라이브러리) |
장점 | 더 읽기 쉽고, 체이닝 가능하며 다양한 컬렉션/예외 조건까지 표현 가능 |
예시
import static org.assertj.core.api.Assertions.*;
@Test
void assertThat_예시() {
assertThat("Hello World")
.startsWith("Hello")
.endsWith("World")
.contains("lo Wo");
assertThat(List.of(1, 2, 3)).containsExactly(1, 2, 3);
}
✅ 3. 주요 assertThat() 체이닝 기능
예시 | 설명 |
assertThat(x).isEqualTo(y) | 값이 같은지 확인 |
assertThat(str).startsWith("ab") | 문자열 시작 확인 |
assertThat(list).contains(1, 2) | 리스트 포함 여부 확인 |
assertThat(map).containsKey("id") | Map 키 존재 확인 |
assertThatThrownBy(() -> ...) | 예외 발생 확인 및 메시지 검증 가능 |
✅ 비교 요약
항목 | assertEquals, assertTrue (JUnit) | assertThat (AssertJ) |
가독성 | 낮음 | 높음 (자연어 느낌) |
표현력 | 제한적 (단순한 값 비교) | 풍부함 (문자열, 컬렉션, 예외까지) |
확장성 | 낮음 | 높음 |
체이닝 지원 | ❌ | ✅ |
✅ 예외 테스트 비교
JUnit 방식
assertThrows(IllegalArgumentException.class, () -> {
someService.doSomething(-1);
});
AssertJ 방식
assertThatThrownBy(() -> {
someService.doSomething(-1);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("잘못된 입력");
💡 실무 팁
- 간단한 테스트: assertEquals, assertTrue 등 JUnit 기본 사용
- 가독성과 디테일 중요할 때: assertThat (AssertJ 추천)
- 대부분 Spring Boot + Gradle 프로젝트에서는 AssertJ가 자동 포함됨 (spring-boot-starter-test 안에 들어있음)
'코딩 > TestCode 공부' 카테고리의 다른 글
Mocking 개념, Test Double 종류 (5) | 2025.06.14 |
---|