YTCW 2024. 10. 16. 12:35

테스트 코드
1. 테스트 코드란?
애플리케이션이 의도한 대로 작동하는지 확인하기 위해서 작성된 코드
개발자가 직접 코드의 결과를 수작업으로 확인하는 대신
자동화된 테스트로 특정 기능이 올바르게 동작하는지를 반복적으로 확인할 수 있다.

2, 테스트 코드의 종류
단위 테스트, 통합 테스트, 기능 테스트, 회귀 테스트

3. given-when-then 패턴
테스트 코드를 세 단계로 구분해 작성하는 방식
given
테스트의 실행을 준비하는 단계
when
테스트를 진행하는 단계
3) then
테스트 결과를 검증하는 단계

 

스프링부트3 테스트 종류

 

spring-boot-starter-test: 테스트를 위한 도구 모음
스프링 부트 스타터 테스트 목록
- JUnit 5 : 자바 프로그래밍 언어용 단위 테스트 프레임워크
: 테스트 방식을 구분할 수 있는 어노테이션을 제공
: @Test를 사용 : 메서드 호출 시 새 인스턴스 생성, 독립테스트 가능


- spring Test & Spring Boot Test : 스프링 부트용 통합 테스트 지원

 

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JUnitTest {
    // @DisplayName : 테스트 이름을 명시
    @DisplayName("1+2는 3이다.")
    // @Test : 테스트를 직접 수행하는 메서드
    @Test
    public void JuniTest1() {
        int a = 1;
        int b = 2;
        int sum = 3;

        Assertions.assertEquals(sum, a+b);
    }
    // .assertEquals
    // : JUnit 에서 제공 하는 검증 메서드
    // : 첫번째 인자값이 두번째 이자값과 같은지 검증
    // : 두번째 인자 - 실제로 검증할 값
}

좌측 실행 버튼 클릭

테스트 성공

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JUnitTest {
    // @DisplayName : 테스트 이름을 명시
    @DisplayName("1+2는 3이다.")
    // @Test : 테스트를 직접 수행하는 메서드
    @Test
    public void JuniTest1() {
        int a = 1;
        int b = 2;
        int sum = 4;

        Assertions.assertEquals(sum, a+b);
    }
    // .assertEquals
    // : JUnit 에서 제공 하는 검증 메서드
    // : 첫번째 인자값이 두번째 이자값과 같은지 검증
    // : 두번째 인자 - 실제로 검증할 값
}

 

실패

 

JUnutCycleTest 파일 추가

import org.junit.jupiter.api.*;

public class JUnitCycleTest {
    // 전체 테스트를 시작하기 전에 1회 실행
    // : static 키워드로 고정된 값을 실행한다.
    // : static 을 사용하는 이유
    // : 객체를 생성하지 않고도 호출 할 수 있고
    // : 클래스가 로드될 떄 한 번만 실행된다.
    @BeforeAll
    // static 정적
    static void beforeAll() {
        System.out.println("Before All");
    }
    // 테스트 케이스를 시작하기 전마다 실행한다.
    @BeforeEach
    public void beforeEach() {
        System.out.println("Before Each");
    }
    @Test
    public void test1() {
        System.out.println("test1");
    }
    @Test
    public void test2() {
        System.out.println("test2");
    }
    @Test
    public void test3() {
        System.out.println("test3");
    }
    // 전체 테스트를 마시고 종료하기 전에 1회 실행하기 떄문에
    // static 메서드로 선언한다.
    @AfterAll
    static void afterAll() {
        System.out.println("After All");
    }
    // 테스트 케이스를 종료하기 전마다 실행한다.
    @AfterEach
    public void afterEach() {
        System.out.println("After Each");
    }
}
// @BeforeAll 클래스 레벨 설정
// @BeforeEach > @Test > @AfterEach 가 테스트 개수만크 반복
// @AfterAll 클래스 레벨 정리

 

이제 활용도가 높은 asserTJ 를 사용해보자

// AsserTJ : 검증문인 Assertion 을 작성하는 데 사용되는 라이브 러리
// : JUnit 과 함께 사용
// : 검증문의 가독성을 향상


// CF) Assertions.assertEquals(sum, a+b);
// 기댓값 : sum
// 식제 비교값 : a+b
// >> 코드만 보고 기댓값과 비교값이 잘 구분되지 않는다.

// AsserTJ 를 사용하여 가독성 향상
// cf) assertThat(a_b).isEqualTo(sum);
// : a 와 b를 더한 값이 sum 과 같아야한다.

 

AsserTJ : 검증문인 Assertion 을 작성하는 데 사용되는 라이브 러리

: JUnit 과 함께 사용
: 검증문의 가독성을 향상


CF) Assertions.assertEquals(sum, a+b);
기댓값 : sum
식제 비교값 : a+b
>> 코드만 보고 기댓값과 비교값이 잘 구분되지 않는다.

AsserTJ 를 사용하여 가독성 향상
Cf) assertThat(a_b).isEqualTo(sum);
: a 와 b를 더한 값이 sum 과 같아야한다.

 

AsserTJ의 메서드

AssertJ의 메서드
- isEqualTo(A): A 값과 같은지 검증
- isNotEqualTo(A): A 값과 다른지 검증
- contains(A): A값을 포함하는지 검증
- doesNotContains(A): A값을 포함하지 않는지 검증
- startsWith(A): 접두사가 A인지 검증
- endsWith(A): 접미사가 A인지 검증
- isEmpty(): 비어있는 값인지 검증
- isNotEmpty(): 비어있지 않은 값인지 검증
- isPositive(): 양수인지 검증
- isNegative(): 음수인지 검증
- isGreaterThan(1): 1보다 큰 값인지 검증
- isLessThan(1): 1보다 작은 값인지 검증

import org.junit.jupiter.api.Test;
// gradle 에서는 기본적으로 src/test/java 폴더를 test파일의 소스경로로 인식
// java 폴더 내의 하위 테스트 클래스에는 static import 를 사용하여
// Assertions 의 메서드를 직업 호출해야한다.
import static org.assertj.core.api.Assertions.assertThat;

//import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class AssertJTest {
    @Test
    public void assertJTest() {
        String name1 = "이승아";
        String name2 = "이도경";
        String name3 = "김명진";

        // 모든 변수가 null 이 아닌지 확인
        assertThat(name1).isNotNull();
        assertThat(name2).isNotNull();
        assertThat(name3).isNotNull();

        // name 1과 name2 가 같은지 확인
        assertThat(name1).isEqualTo(name2);

        // name 1과 name3 이 다른지 확인
        assertThat(name1).isEqualTo(name3);
    }
}

 

 

package org.example.springbootdeveloper.controller;

import org.example.springbootdeveloper.entity.Member;
import org.example.springbootdeveloper.repository.MemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

// 메인 애플리케이션 클래스에 있는 @SpringBootApplication을 찾고
// : 해당 클래스의 빈을 찾아 테스트용 애플리케이션 컨텍스트를 생성
@SpringBootTest
// MockMvc를 생성하고 자동으로 구성하는 애너테이션
// : 컨트롤러 테스트 시 사용
@AutoConfigureMockMvc
class TestControllerTest {

    @Autowired
    protected MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private MemberRepository memberRepository;

    @BeforeEach
    public void mockMvcSetUp() {
        // 테스트 실행 전 메서드
        // : MockMvc 설정
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .build();
    }

    @AfterEach
    public void cleanUp() {
        // member 테이블에 있는 데이터를 모두 삭제
        memberRepository.deleteAll();
    }

    // 실제 테스트 코드 작성
    @DisplayName("getAllMembers: 조회 성공")
    @Test
    public void getAllmembers() throws Exception {
        // given
        final String url = "/test";
        Member savedMember = memberRepository.save(new Member(1L, "김다혜"));

        // when
        // .perform() 메서드: 요청을 전송하는 역할
        // - 결과를 ResultActions 객체로 받음
        // - ResultActions 객체는 반환값을 검증하고 확인하는 andExpect() 메서드를 제공
        final ResultActions result = mockMvc.perform(get(url)
                // .accept(): 요청을 보낼 때 무슨 타입으로 응답 받을지 결정하는 메서드
                // - JSON, XML 등이 있음
                .accept(MediaType.APPLICATION_JSON));

        // then
        result
                // andExpect() 메서드: 응답을 검증
                // - TestController에서 만든 API는 응답으로 OK(200)을 반환
                // - .isOk() 메서드로 응답코드가 맞는지 확인
                .andExpect(status().isOk())
                // jsonPath("$[0].필드명")
                // : JSON의 응답값을 가져오는 역할을 담당
                // : 0번째 배열에 들어있는 객체의 id, name을 가져오고 저장된 값과 같은지 확인
                .andExpect(jsonPath("$[0].id").value(savedMember.getId()))
                .andExpect(jsonPath("$[0].name").value(savedMember.getName()));
    }
}
// HTTP 주요 응답 코드
// 200 OK - isOk()
// 201 Created - isCreated()
// 400 Bad Request - isBadRequest()
// 403 Forbidden - isForbidden()
// 404 Not Found - isNotFound()
// 400번대 응답 코드 - is4xxClientError()
// 500 Internal Server Error - isInternalServerError()
// 500번대 응답 코드 - is5xxServerError()