코딩/TestCode 공부

JUnit

americanoallday 2025. 6. 1. 17:26
항목 설명
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에 연결하지 않고도 UserRepositoryfindById()를 실행한 것처럼 동작시킬 수 있음.

 


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