본문 바로가기
개인공부/스프링 부트 3 백엔드 개발자 되기

스프링 부트 - 4장_스프링부트3의 테스트

by 응가1414 2023. 6. 29.

테스트 코드 개념 익히기

유지보수에도 매우 좋고, 코드 수정 시 기존 기능이 제대로 작동하지 않을까봐 걱정하지 않아도 된다는 장점이 있는

테스트 코드 공부를 해야한다.

테스트 코드란?

  1. 테스트 코드에도 다양한 패턴이 있다.
    1. Given-when-then 패턴을 공부하자
      1. given은 테스트 실행을 준비하는 단계
      2. when은 테스트를 진행하는 단계
      3. then은 테스트 결과를 검증하는 단계
@DisplayName("새로운 메뉴를 저장한다.")
@Test
public void saveMenuTest(){
  // given : 메뉴를 저장하기 위한 준비 과정
  final String name = "아메리카노";
  final int price = 2000;
  
  final Menu americano = new Menu(name, price);
  
  // when : 실제로 메뉴를 저장
  final long saveId = menuService.save(americano);
  
  // then : 메뉴가 잘 추가되었는지 검증
  final Menu savedMenu = menuService.findById(saveId).get();
  assertThat(savedMenu.getName()).isEqualTo(name);
  assertThat(saveMenu.getPrice()).isEqualTo(price);
}

Given: 메뉴를 저장힉 위해 준비하는 과정인

When : 메뉴를 저장하는

Then: 메뉴가 잘 추가되었는지 검증하는

  • 이중에 JUnit 과 AssertJ를 가장 많이 사용한다.

JUnit 이란?

  1. JUnit은 자바 언어를 위한 단위 테스트 프레임 워크이다.
    1. 단위 테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미
    2. JUnit을 사용하면 단위 테스트를 작성하고 테스트를 하는데 도움을 준다.
  2. 사용법이 간단해 쉽게 익힐 수 있다.
    1. 테스트 결과가 직관적이라 좋다.
  3. 구체적인 JUnit의 특징은 다음과 같다.
Junit의 특징
- 테스트 방식을 구분할 수 있는 애너테이션을 제공
- @Test do너테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트 가능
- 예상 결과를 검증하는 어설션 메서드 제공
- 사용 방법이 단순, 테스트 코드 작성 시간이 적음
- 자동 실행, 자체 결과를 확인하고 즉각적인 피드백을 제공

Junit으로 단위 테스트 코드 만들기

[src -> test -> java] 폴더에 JUnitTest.java 파일 생성

public class JUnitTest {
  @DisplayName("1 + 2smㄴ 3이다.") // 테스트 이름
  @Test // 테스트 메서드
  public void junitTest(){
    int a = 1;
    int b = 2;
    int sum = 3;
    
    Assertions.assertEquals(a + b, sum); // 값이 같은지 확인
  }
}
  1. @DisplayName 애너테이션은 테스트 이름을 명시
  2. @Test 애너테이션을 붙인 메서드는 테스트를 수행하는 메서드
  3. JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고 테스트가 종료되면 실행 객체를 삭제한다.
  4. assertEquals() 메서드의 첫 번째 인수에는 기대하는 값, 두번째 인수에는 실제로 검증할 값을 넣어준다.

JUnitCycleTest 테스트 파일의 사이클을 확인하자.

import org.junit.jupiter.api.*;
​
public class JUnitCycleTest {
  @BeforeAll // 전체 테스트를 시작하기 전에 1회 실행하므로 메서드는 static으로 선언
  static void beforeAll() {
    // 전체 테스트가 시작하기 전에 실행
    System.out.println("@BeforeAll");
  }
  
  @BeforeEach // 테스트 케이스를 시작하기 전마다 실행
  public void beforeEach() {
    System.out.println("@BeforeEach");
  }
  
  @Test
  public void test1(){
    System.out.println("test1");
  }
  
  @Test
  public void test2(){
    System.out.println("test2");
  }
  
  @Test
  public void test3(){
    System.out.println("test3");
  }
  
  @AfterAll // 전체 테스트를 마치고 종료하기 전에 1회 실행하므로 메서드는 static으로 선언
  static void afterAll(){
    System.out.println("@AfterAll");
  }
  
  @AfterEach // 테스트 케이스를 종료하기 전마다 실행
  public void afterEach(){
    System.out.println("@AfterEach");
  }
}
  • @BeforeAll 에너테이션
    • 전체 테스트를 시작하기 전에 처음으로 한 번만 실행
      • 데이터베이스를 연결해야 하거나 테스트 환경을 초기화할 때 사용
      • 전체 테스트 실행 주기에서 한 번만 호출되어야 되서 메서드를 static으로 선언
  • @BeforeEach 애너테이션
    • 테스트 케이스를 시작하기 전에 매번 실행한다.
      • 테스트메서드에서 사용하는 객체를 초기화하거나 테스트에 필요한 값을 미리 넣을 때 사용가능
    • 각 인스턴스에 대해 메서드를 호출해야 하므로 메서드는 static이 아니어야 한다.
  • @AfterAll 애너테이션
    • 전체 테스트를 마시고 종료하기 전에 한 번만 실행한다.
      • 데이터베이스 연결을 종료할 때나 공통적으로 사용하는 자원을 해제할 때 사용
    • 전체 테스트 실행 주기에서 한번만 호출되어야 하므로 메서드를 static으로 선언
  • @AfterEach 애너테이션
    • 각 테스트 케이스를 종료하기 전 매번 실행
      • 테스트 이후에 특정 데이터를 삭제해야 하는 경우에 사용
    • @BeforeEach 애너테이션과 마찬가지로 메서드는 Static이 아니어야한다.

@BeforeAll -> @BeforeEach -> @Test_1 -> @Test_2 -> @Test_3 -> @AfterEach
​
생명주기로 테스트가 진행

AssertJ로 검증문 가독성 높이기

  1. Assertion은 기댓값과 실제 비교값을 명시하지 않으므로 비교 대상이 헷갈린다.
- 가독성이 안좋다. Assertion
Assertions.assertEquals(a + b, sum);
​
- 가독성이 좋은 AssertJ
assertThat(a + b).isEqualTo(sum);

메서드설명

isEqualTo(A) 주어진 값 A와 동일한지 검증
isNotEqualTo(A) 주어진 값 A와 다른지 검증
contains(A) 주어진 값 A를 포함하는지 검증
doesNotContain(A) 주어진 값 A를 포함하지 않는지 검증
startsWith(A) 주어진 값 A로 시작하는지 검증
endsWith(A) 주어진 값 A로 끝나는지 검증
isEmpty() 비어있는 값인지 검증
isNotEmpty() 비어있지 않은 값인지 검증
isPositive() 양수인지 검증
isNegative() 음수인지 검증
isGreaterThan(1) 주어진 값 1보다 큰지 검증
isLessThan(1) 주어진 값 1보다 작은지 검증

테스트 코드 작성

// TestControllerTest.java
​
@SpringBootTest // 테스트용 애플리 케이션 컨택스트 생성
@AutoContigureMockMvc // MockMvc 생성
class TestControllerTest {
  @Autowired
  protected MockMvb mockMvc;
  
  @Autowired
  private WebApplicationContext context; //웹 환경과 통합, 서블릿 기반의 컴포넌트 스캔
  
  @Autowired
  private MemberRepository memberRepository;
  
  @BeforeEach // 테스트 실행 전 실행하는 메서드
  public void mockMvcSetUp(){
    this.mockMvc = MockMvcBuilders.webAppContextSetup(context) // MockMVC 설정
      .build();
  }
  
  @AfterEach // 테스트 실행 후 실행하는 메서드
  public void cleanUp(){
    // 테스트를 실행한 이후에 실행하는 애너테이션
    // 테스트종료후에 member 테이블에 있는 데이터들을 모두 삭제
    memberRepository.deleteAll(); 
  }
}
  • @SpringBootTest
    • 메인 애플리케이션 클래스에 추가하는 애너테이션인 @SpringBootApplication이 있는 클래스를 찾고
    • 클래스에 포함되어 있는 빈을 찾은 다음
    • 테스트용 애플리 케이션 컨텍스트라는 것을 만든다.
  • AutoConfigureMockMvb
    • MockMvb를 생성하고 자동으로 구성하는 애너테이션
    • MockMvb는 어플리케이션을 서버에 배포하지 않고도
      • 테스트롤 MVC 환경을 만들어 요청 및 전송, 응답 기능을 제공하는 유틸리티 클래스입니다.
    • 컨트롤러를 테스트할 때 사용되는 클래스
  • @BeforeEach
    • 테스트를 실행하기 전에 실행하는 메서드에 적용하는 애너테이션
    • MockMvcSetUp() 메서드를 실행해 MockMvc를 설정해줍니다.
  • AfterEach
    • 테스트를 실행한 이후에 실행하는 메서드에 적용하는 애너테이션.
    • cleanUp() 메서드를 실행해 member 테이블에 있는 데이터들을 모두 삭제

TestController의 로직을 테스트하는 코드를 작성

// TestControllerTest.java
​
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 생성
@AutoConfigureMockMvc // MockMvb 생성
class TestControllerTest {
  @Autowired
  protected MockMvc mockMvc;
  
  @Autowired
  private WebApplicationContext context; //웹 환경과 통합, 서블릿 기반의 컴포넌트 스캔
  
  @Autowired
  private MemberRepository memberRepository; // Member entity와 상호작용할수 있는 Repository연결
​
  @BeforeEach // 테스트 실행 전 실행하는 메서드, MockMVC 설정
  public void mockMvcSetUp(){
    this.mockMvc = MockMvcBuilders.webAppContextSetup(context) // MockMVC 설정
            .build();
  }
  
  @AfterEach // 테스트 실행 후 실행하는 메서드
  public void cleanUp(){
    memberRepository.deleteAll();
  }
  
  // -------------------------------- 이후 새로운 코드
  @DisplayName("getAllMembers: 아티클 조회에 성공한다.")
  @Test
  public void getAllMembers() throws Exception {
    // given
    final String url = "/test";
    Member savedMember = memberRepository.save(new Member(1L, "홍길동"));
    
    // when
    final ResultActions result = mockMvc.perform(get(url) // 블로그글 추가 API에 요청을 보낸다.
             .accept(MediaType.APPLICATION_JSON) // 요청타입은. JSON이다.
          );
    
    // then
    result
          .andExpect(status().isOk()) // 응답 코드가 200인지 확인
          // 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다.
          .andExpect(jsonPath("$[0].id").value(savedMember.getId()))
          .andExpect(jsonPath("$[0].name").value(savedMember.getName()));
  }
}

테스트 방법론내용

Given 멤버를 저장합니다.
When 멤버 리스트를 조회하는 API를 호출합니다.
Then 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다.

MockMVC에 대한 메서드 설명

// MockMVC에 대한 메서드 설명   
final ResultActions result = mockMvc.perform(get(url) // 블로그글 추가 API에 요청을 보낸다.
             .accept(MediaType.APPLICATION_JSON) // 요청타입은. JSON이다.
          );
​
result
      .andExpect(status().isOk()) // 응답 코드가 200인지 확인
      // 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다.
      .andExpect(jsonPath("$[0].id").value(savedMember.getId()))
      .andExpect(jsonPath("$[0].name").value(savedMember.getName()));
  • Perform() 메서드는
    1. 요청을 전송하는 역할을 하는 메서드
    2. ResultActions 객체를 받으며
    3. ResultActions 객체는 반환값을 검증하고 확인하는 andExpect() 메서드를 제공
  • Accept() 메서드는
    1. 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드입니다.
    2. JSON, XML 들 다양한 타입이 있지만
      1. 여기서는 JSON을 받는다고 명시하자.
  • andExpect() 메서드는
    1. 응답을 검증한다.
    2. TestController에서 만든 API는 응답으로 OK(200)을 반환하므로 이에 해당하는 메서드인
      1. isOk를 사용해 응답 코드가 OK(200)인지 확인합니다.
  • jsonPath("$[0].${필드명}") ( OR , jsonPath("$.${필드명}") )은
    1. JSON 응닶값의 값을 가져오는 역할을 하는 메서드입니다.
      1. 0번째 배열에 들어있는 객체의 id, name값을 가져오고
      2. 저장된 값과 같은지 확인합니다.

테스트 프레임워크에서 HTTP응답 코드를 검증하는 데 사용하는 메서드

HTTP 주요 응답 코드

코드매핑 메서드설명

200 OK isOk() HTTP 응답 코드가 200 OK인지 검증
201 Created isCreated() HTTP 응답 코드가 201 Created인지 검증
400 Bad Request isBadRequest() HTTP 응답 코드가 400 Bad Request인지 검증
403 Forbidden isForbidden() HTTP 응답 코드가 403 Forbidden인지 검증
404 Not Found isNotFound() HTTP 응답 코드가 404 Not Found인지 검증
400번대 응답 코드 is4xxClientError() HTTP 응답 코드가 400번대 응답 코드인지 검증
500 Internal Server Error isInternalServerError() HTTP 응답 코드가 500 Internal Server Error인지 검증
500번대 응답 코드 is5xxServerError() HTTP 응답 코드가 500번대 응답 코드인지 검증

4장 핵심요약

  • 테스트 코드를 작성하면 코드의 기능이 제대로 작동한다는 것을 검증할 수 있다.
    1. 테스트코드는 보통 테스트를 준비하는 given
    2. 테스트를 실제로 진행하는 when
    3. 테스트 결과를 검증하는 then
  • JUnit은 단위 테스를 할 때 사용하는 자바 테스트 프레임워크입니다. - 테스트 에너테이션의 흐름은
    1. @BeforeAll 애너테이션으로 설정한 메서드가 실행되고
    2. 그 이후에는 테스트 케이스 개수만큼 @BeforeEach -> @Test -> @AfterEach의 생명주기를 가지고 실행합니다.
    3. 모든 테스트가 완료되면 마지막으로 @AfterAll 애너테이션으로 설정한 메서드가 실행되고 종료됩니다.
  • AssertJ는
    1. JUnit과 함께 사용해 검증문의 가독성을 확 높여주는 라이브러리입니다.