[Spring Boot] TDD와 given-when-then 패턴으로 테스트 코드 작성 (JUnit, AssertJ, 단위 테스트, 통합 테스트, Mock 등)

2024. 9. 11. 23:17· Spring Boot
목차
  1. 1. 테스트
  2. 1-1. TDD (Test Driven Development)
  3. 1-2. 테스트 유형
  4. 2. JUnit 5
  5. 2-1. 테스트 메서드
  6. 2-2. JUnit 테스트의 생명주기
  7. 2-3. Assertions Method
  8. 3. given-when-then 패턴
  9. 4. 단위 테스트와 Mockito
  10. 4-1. (단위 테스트) 직접 mock 객체 생성, 주입
  11. 4-2. (단위 테스트) 어노테이션으로 자동 mock 객체 생성, 주입
  12. 5. 통합 테스트
  13. 5-1. 관련 어노테이션 정리

1. 테스트

1-1. TDD (Test Driven Development)

  • 테스트 주도 개발
  • 테스트 코드를 먼저 작성 → 작성한 테스트 코드를 통과하게끔 실제 코드 작성하는 개발 방법

 

  • TDD 주요 원칙

  1. 테스트 실패 확인
    • 실제 코드를 구현하기 전 테스트 케이스를 작성함
    • 요구사항을 테스트로 변환함으로써 명확한 목표 설정 가능!
  2. 테스트 통과 가능 최소 코드 작성
    • 테스트 통과에 꼭 필요한 최소한의 코드만 작성함
    • YAGNI (You Aren’t Gonna Need It) 원칙 적용
  3. 코드 개선 리펙토링
    • 테스트 통과한 최소 코드를 개선함
    • 중복 제거, 가독성 향상을 목표 ⇒ 설계가 개선되고 유지보수성이 증가함!

 

1-2. 테스트 유형

1-2-1. 단위 테스트

  • 메서드, 클래스와 같이 개별 코드 단위를 검증하는 테스트
  • 격리된 환경에서 실행

1-2-2. 통합 테스트

  • 여러 단위 (메서드, 클래스)를 조합하여 상호작용을 검증하는 테스트
  • 모듈 간 인터페이스 테스트

1-2-3. 시스템 테스트

  • 전체 시스템의 end-to-end 기능을 검증하는 테스트
  • 비기능적 요구사항 (성능, 보안, 사용성 등) 테스트함
    ex) 스트레스 테스트, 부하 테스트 등

1-2-4. 인수 테스트

  • 최종 사용자 관점에서 검증하는 테스트
  • 비즈니스 요구사항이 충족되는지 여부를 확인함

 

2. JUnit 5

  • JUnit : 자바를 위한 대표적인 단위 테스팅 프레임워크
  • 스프링 부트 버전 2.2 이상 → 자동으로 JUnit 5 의존성 추가됨

 

2-1. 테스트 메서드

  • @Test 어노테이션 붙여야 함
  • 접근제어자 : private 이면 안됨!
  • 반환 타입 : void 여야 함!
  • 메서드는 파라미터를 받지 않아야 함
  • 테스트가 Assertion 예외를 발생시키지 않으면 성공!

 

2-2. JUnit 테스트의 생명주기

2-2-1. 테스트 진행 과정

  1. 테스트 클래스 인스턴스를 새로 생성함
  2. 생성한 인스턴스가 가지고 있는 테스트 환경준비 (setup) 메서드를 모두 찾아 호출함
    • 테스트 환경준비 (setup) 메서드 : 각 테스트가 실행되기 전에 호출되는 메서드 ⇒ 테스트에 필요한 초기 설정 가능!
  3. 테스트 메서드 호출함
  4. 클래스 인스턴스가 가지고 있는 테스트 환경정리 (teardown) 메서드를 모두 찾아 호출함
    • 테스트 환경정리 (teardown) 메서드 : 각 테스트가 종료된 후에 호출되는 메서드
      ⇒ 테스트로 사용한 리소스 해제하거나 데이터 삭제 가능!

 

2-2-2. 생명 주기 어노테이션

  • @Test : 실제 테스트 케이스 정리
  • @BeforeEach : 각 테스트 메서드가 실행되기 전에 호츌
  • @AfterEach : 각 테스트 메서드가 실행된 후에 호출
  • @BeforeAll : 테스트 클래스 (모든 테스트 메서드) 가 실행되기 전에 호출 / static 으로 선언해야 함!
  • @AfterAll : 테스트 클래스 (모든 테스트 메서드) 가 실행된 후에 호출 / static 으로 선언해야 함!

 

  • 코드로 확인
public class JUnitCycleTest {

    @BeforeAll 
    public static void beforeAll() {
        System.out.println("JUnitCycleTest.beforeAll");
    }

    @BeforeEach
    public void beforeEach() {
        System.out.println("JUnitCycleTest.beforeEach");
    }

    @AfterAll
    public static void afterAll() {
        System.out.println("JUnitCycleTest.afterAll");
    }

    @AfterEach
    public void afterEach() {
        System.out.println("JUnitCycleTest.afterEach");
    }

    @Test
    public void test1() {
        System.out.println("JUnitCycleTest.test1");
    }
    @Test
    public void test2() {
        System.out.println("JUnitCycleTest.test2");
    }
    @Test
    public void test3() {
        System.out.println("JUnitCycleTest.test3");
    }
}

/* 테스트 실행 결과
JUnitCycleTest.beforeAll
JUnitCycleTest.beforeEach
JUnitCycleTest.test1
JUnitCycleTest.afterEach
JUnitCycleTest.beforeEach
JUnitCycleTest.test2
JUnitCycleTest.afterEach
JUnitCycleTest.beforeEach
JUnitCycleTest.test3
JUnitCycleTest.afterEach
JUnitCycleTest.afterAll
*/

 

2-3. Assertions Method

  • 테스트 코드 내에서 특정 조건이 참인지를 확인하는 메서드
    ⇒ 테스트 케이스의 실행 결과가 예상된 결과와 일치하는 지 확인 가능!

 

  • 대표적으로 두 라이브러리 존재
    • Junit (Assertions) : JUnit 5에서 제공하는 기본 assertion 메서드
    • AssertJ (Assertions) : AssertJ 라이브러리에서 제공하는 assertion 메서드

 

2-3-1. Junit (Assertions)

  • 아래 import 문으로 메서드 가져옴
import static org.junit.jupiter.api.Assertions.*;
  • 대표적인 메서드
    • assertEquals(기댓값, 실제값) : 기댓값과 실제값이 일치하면 통과
    • assertNotEquals(기대x값, 실제값) : 기대x값과 실제값이 일치하지 않으면 통과
    • assertTrue(조건) : 조건이 참이면 통과
    • assertFalse(조건) : 조건이 거짓이면 통과
    • assertNull(Object obj) : 객체가 null 이면 통과
    • assertNotNull(Object obj) : 객체가 null 이 아니면 통과
    • assertSame(기댓값, 실제값) : 기댓값과 실제값이 동일한 객체를 참조하면 통과
    • assertNotSame(기대x값, 실제값) : 기대x값과 실제값이 동일한 객체를 참조하지 않으면 통과
    • assertArrayEquals(기대 array, 실제 array) : 기대 array와 실제 array의 요소 순서와 값이 모두 동일하면 통과
    • assertThrows(기대 예외, executable) : executable이 기대한 exception을 발생시키면 통과

 

2-3-2. AssertJ (Assertions)

  • 아래 import 문으로 메서드 가져옴
import static org.assertj.core.api.Assertions.*;
  • 대표적인 메서드 (마지막 메서드 제외하고 assertThat(실제값) 뒤에 메서드 체이닝 형식으로 붙임)
    • .isEqualTo(기댓값) : 실제값이 기댓값과 같으면 통과
    • .isNotEqualTo(기대x값) : 실제값이 기대x값과 다르면 통과
    • .isTrue() : 조건 이 참이면 통과
    • .isFalse() : 조건이 거짓이면 통과
    • .isNull() : 객체가 null이면 통과
    • .isNotNull() : 객체가 null이 아니면 통과
    • .isSameAs(기댓값) : 실제값과 기댓값이 동일한 객체를 참조하면 통과
    • .isNotSameAs(기대x값) : 실제값과 기대x값이 동일한 객체를 참조하지 않으면 통과
    • .containsExactly(기대 array) : 배열이 기대 array와 요소 순서와 값이 모두 동일하면 통과
    • assertThatThrownBy(() -> executable).isInstanceOf(기대 예외) : executable이 기대한 예외를 발생시키면 통과

 

3. given-when-then 패턴

  • 테스트 구조를 명확하게 나타낼 수 있음
  • Given
    • 테스트에서 전제 조건을 설정하는 단계
      ex) 테스트에 필요한 변수, 객체 등의 생성 등
  • When
    • 실제 테스트하려는 로직을 실행하는 단계
  • Then
    • 테스트 결과를 검증하는 단계
    • 기댓값과 실제값이 일치하는지 비교 → 테스트 성공 여부 판단
  • 예시 코드
@Test
public void sumTest(){
    //given
    int a = 1;
    int b = 2;

    //when
    int result = a+b;

    //then
    Assertions.assertEquals(a+b, result);
}

 

4. 단위 테스트와 Mockito

  • 단위 테스트
    • 개별 모듈이나 메서드 등의 동작을 확인하는 테스트
    • 격리된 환경에서 진행하며 의존성을 최소화함!
  • Mockito
    • 테스트를 위해 사용되는 Mocking 프레임워크
    • (주로 단위 테스트에서) 의존성 객체를 모의(=가짜)로 만들어서 실제 객체를 대체 ⇒ 테스트 수행하도록 도와줌!
import static org.mockito.Mockito.*;

 

📌 mock 객체

: mockito로 생성한 모의 객체 (실제 객체의 행동을 모방)
  • 주요 기능
    1. Mock (모킹) : 실제 객체를 모방하는 모의 객체를 생성해서 객체의 행동을 모방함
    2. Stub (스텁) : 실제 메서드를 호출하는 것이 아닌 미리 정의해 둔 값을 반환하도록 설정
    3. verification (검증) : 특정 메서드가 호출되었는지 혹은 몇 번 호출되었는지 검증 가능
    4. chaining (체이닝) : 메서드 호출 체이닝 가능

 

4-1. (단위 테스트) 직접 mock 객체 생성, 주입

  • mock(mocking할 클래스) : mocking할 클래스에 대해 mock 객체 생성
public class MemberServiceTest {

    @Test
    @DisplayName("Mock 예제 테스트")
    public void testService() {

        //given

            // 의존성 객체를 모킹
        MemberRepository memberRepository = mock(MemberRepository.class);

            // Member 엔티티 생성
        String name = "홍길동";
        int age = 20;
        Member member = Member.builder().name(name).age(age).build();

            // 메서드 스텁 설정 -> 임의로 생성한 member 리턴!
        when(memberRepository.findById(1L)).thenReturn(Optional.ofNullable(member));

            // 테스트할 객체 생성 -> 모킹한 의존성 객체 주입
        MemberService memberService = new MemberService(memberRepository);


        //when

            // 메서드 호출
        Member foundMember = memberService.findById(1L);


        // then
            // 결과 검증 -> JUnit
        assertEquals(name, foundMember.getName());
            // 결과 검증 -> AssertJ
        assertThat(foundMember.getAge()).isEqualTo(age);

            // 메서드 호출 검증
        verify(memberRepository, times(1)).findById(1L);
    }
}

 

4-2. (단위 테스트) 어노테이션으로 자동 mock 객체 생성, 주입

  • @Mock : mock 객체를 자동 생성해주는 어노테이션
  • @InjectMocks : 인스턴스 자동 생성 + 필요한 의존관계에 대해 mock 객체를 자동 주입해줌
  • MockitoAnnotations.openMocks(this) : 현재 클래스에서 사용된 모든 @Mock, @InjectMocks 등의 어노테이션 처리
    ⇒ mock 객체 초기화
public class MemberServiceTest {

    @Mock  // mock 객체 자동 생성
    private MemberRepository memberRepository;

    @InjectMocks  // 인스턴스 생성 후 mock 객체 관련 의존관계 자동 주입
    private MemberService memberService;

    @BeforeEach  // 각 테스트 케이스 실행 전 수행됨
    void setUp() {
        MockitoAnnotations.openMocks(this);  // mock 객체 초기화
    }

    @Test
    @DisplayName("Mock 예제 테스트")
    public void testService() {

        //given

            // Member 엔티티 생성
        String name = "홍길동";
        int age = 20;
        Member member = Member.builder().name(name).age(age).build();

            // 메서드 스텁 설정 -> 임의로 생성한 member 리턴!
        when(memberRepository.findById(1L)).thenReturn(Optional.ofNullable(member));


        //when

            // 메서드 호출
        Member foundMember = memberService.findById(1L);


        // then
            // 결과 검증 -> JUnit
        assertEquals(name, foundMember.getName());
            // 결과 검증 -> AssertJ
        assertThat(foundMember.getAge()).isEqualTo(age);

            // 메서드 호출 검증
        verify(memberRepository, times(1)).findById(1L);

    }
}

 

5. 통합 테스트

  • 여러 모듈 or 컴포넌트 등을 결합하여 전체 시스템의 동작을 검증하는 테스트
    ⇒ 모듈 간 상호작용 및 인터페이스의 정확성 확인 가능함!
📌 단위 테스트 vs 통합 테스트

단위 테스트 : 위의 코드 예시와 같이 의존관계 최소화를 위해 mock 객체 사용, 주입
통합 테스트 : 여러 클래스, 컴포넌트들의 상호작용을 테스트하는 것 ⇒ 실제 등록된 빈등을 통해 의존관계 주입

 

5-1. 관련 어노테이션 정리

5-1-1. @SpringBootTest

  • 스프링 부트에서 통합 테스트를 수행하기 위한 어노테이션
  • 스프링 어플리케이션의 전체 context를 로드함 ⇒ 모든 빈, 설정, 컴포넌트가 실제 환경에서 동작하는 것처럼 테스트 가능함!
@SpringBootTest
class MemberApiControllerTest {
    // 테스트 코드 작성
}

 

5-1-2. @AutoConfigureMockMvc

  • 스프링 부트에서 제공, MockMvc 설정을 자동으로 가능하게 해줌
  • 실제 웹 서버를 가동하지 않고도 HTTP 요청 ↔ 응답을 테스트 가능하게 함! (컨트롤러 테스트 환경)
  • @Autowired 를 통해 MockMvc 인스턴스 주입 가능
@SpringBootTest
@AutoConfigureMockMvc
class MemberApiControllerTest {
		
    @Autowired
    private MockMvc mockMvc;
		
    // 테스트 코드 작성
}

 

5-1-3. MockMvc

  • 웹 서버 가동 없이 스프링 MVC의 동작을 재현
  • HTTP 요청에 대한 시뮬레이션을 제공함
    • GET : get(url)
    • POST : post(url)
    • PUT : put(url)
    • DELETE : delete(url)

 

  • HTTP 요청 설정 메서드
    • .header(key, value) : 요청 헤더 설정
    • .contentType(타입) : 요청 본문의 타입 설정
    • .content(문자열) : 요청 본문의 내용 설정
    • .param(key, value) : 요청 파라미터 설정

 

  • HTTP 응답 검증 메서드
    • status() : 응답 코드 확인 메서드
      • .isOk(), .isCreated(), .isBadRequest() 등
    • header() : 응답 헤더 확인 메서드
      • .string(key, value) 등
    • jsonPath(”$.필드”).value(실제값) : JSON 응답의 필드 값이 실제값과 일치하는 지 확인
    • jsonPath(”$[인덱스].필드”).value(실제값) : 리스트 응답에서 해당 인덱스의 JSON 의 필드 값이 실제값과 일치하는 지 확인

 

📌 MockMvc 이용 HTTP 요청, 응답 테스트 예시

MockMvc의 perform() 메서드 사용!
⇒ perform() 메서드의 반환 타입은 ResultActions 임

- GET 요청 예시 : get()

- POST 요청 예시 : post()


 

저작자표시 변경금지 (새창열림)

'Spring Boot' 카테고리의 다른 글

[Spring Boot] 스프링 이미지 업로드 예제 (4) - AWS S3에 이미지 업로드  (0) 2024.11.07
[Spring Boot] 스프링 로깅 방법 (SLF4J, Logback)  (0) 2024.09.15
[Spring Boot] 주소 입력으로 날씨 정보 출력 예제 프로젝트 (Geocoder API 2.0, open weather map API)  (0) 2024.09.08
[Spring Boot] Jpa Auditing (+ BaseEntity) 로 엔티티 관련 이벤트 자동 기록하기  (2) 2024.09.07
[Spring Boot] JpaRepository 개념, 사용 방법 (쿼리 메서드, @Query, JPQL, Native Query)  (0) 2024.09.05
  1. 1. 테스트
  2. 1-1. TDD (Test Driven Development)
  3. 1-2. 테스트 유형
  4. 2. JUnit 5
  5. 2-1. 테스트 메서드
  6. 2-2. JUnit 테스트의 생명주기
  7. 2-3. Assertions Method
  8. 3. given-when-then 패턴
  9. 4. 단위 테스트와 Mockito
  10. 4-1. (단위 테스트) 직접 mock 객체 생성, 주입
  11. 4-2. (단위 테스트) 어노테이션으로 자동 mock 객체 생성, 주입
  12. 5. 통합 테스트
  13. 5-1. 관련 어노테이션 정리
'Spring Boot' 카테고리의 다른 글
  • [Spring Boot] 스프링 이미지 업로드 예제 (4) - AWS S3에 이미지 업로드
  • [Spring Boot] 스프링 로깅 방법 (SLF4J, Logback)
  • [Spring Boot] 주소 입력으로 날씨 정보 출력 예제 프로젝트 (Geocoder API 2.0, open weather map API)
  • [Spring Boot] Jpa Auditing (+ BaseEntity) 로 엔티티 관련 이벤트 자동 기록하기
공대생안씨
공대생안씨
전자공학과 학부생의 코딩 일기
티스토리
|
로그인
공대생안씨
공대생의 코딩 일기
공대생안씨
글쓰기
|
관리
전체
오늘
어제
  • All Categories (152)
    • Spring Boot (55)
      • JPA (7)
      • Lombok (2)
    • Java (21)
    • DevOps (12)
      • CI,CD (8)
      • Monitoring (1)
    • Database (7)
      • MySQL (5)
      • MongoDB (1)
      • H2 (1)
    • Trouble Shooting (5)
    • FE (4)
    • IntelliJ (3)
    • Git (3)
    • Algorithm (41)

블로그 메뉴

  • 홈
  • 태그
  • Github

공지사항

인기 글

hELLO · Designed By 정상우.v4.2.2
공대생안씨
[Spring Boot] TDD와 given-when-then 패턴으로 테스트 코드 작성 (JUnit, AssertJ, 단위 테스트, 통합 테스트, Mock 등)
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.