[첫번째 프로젝트] 1. 테스트 코드

DAEILLIM·2024년 1월 6일
0

첫번째 프로젝트

목록 보기
1/17
post-thumbnail

0. 테스트 코드 배우는 이유

테스트 코드는 작성된 코드가 의도대로 잘 동작하고 예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드입니다. 보통 스프링 부트 강의와 서적에 테스트 코드도 함께 내용이 담겨 있지만, 턱없이 부족하다고 느꼈습니다.😂

개발자에게 반드시 필요한 테스트 케이스를 프로젝트에 채택한다면, 유지보수에도 매우 좋고, 코드 수정 시 기존 기능이 제대로 작동하지 않을까 봐 걱정하지 않아도 된다는 장점이 있기 때문에 테스트 코드에 대한 내용을 잘 정리하여 프로젝트에 성공적으로 반영하는 것이 목표입니다.✨


1. 테스트 코드

테스트 코드는 다양한 패턴이 존재합니다. 주로 인프런과 서적으로 학습하면서 자주 사용한 패턴인 given-when-then 패턴을 채택할 것입니다. given-when-then 패턴은 테스트 코드를 세 단계로 구분하여 작성하는 방식으로 의미는 다음과 같습니다.

종류설명
given테스트 실행을 준비하는 단계
when테스트를 진행하는 단계
then테스트 결과를 검증하는 단계

예를 들어 새로운 메뉴를 저장하는 코드를 테스트한다고 가정했을 때 given-when-then 패턴의 테스트 코드는 다음과 같습니다.

@Displayname("새로운 메뉴를 저장한다.")
@Test
public void saveMenuTest() {
    // given: 메뉴를 저장하기 위한 준비 과정
    final String name = "아메리카노";
    final int price = 2000;
    
    final Menu americano = new Menu(name, price);
    
    // when : 실제로 메뉴를 저장
    final long savedId = menuService.save(americano);
    
    // then : 메뉴가 잘 추가되었는지 검증
    final Menu savedMenu = menuService.findById(savedId).get();
    assertThat(savedMenu.getName()).isEqualTo(name);
    assertThat(savedMenu.getPrice).isEqualTo(price);
}

2. 스프링 부트3와 테스트

spring-boot-starter-test 안에 테스트를 위한 다양한 도구가 모여 있습니다. 스프링 부트 스타터 테스트 목록은 다음과 같습니다.

  1. JUnit : 자바 프로그래밍 언어용 단위 테스트 프레임워크
  2. Spring Test & Spring Boot Test : 스프링 부트 애플리케이션을 위한 통한 테스트 지원
  3. AssertJ : 검증문이 어설션을 작성하는 데 사용되는 라이브러리
  4. Hamcrest : 표현식을 보다 이해하기 쉽게 만드는 데 사용되는 Matcher 라이브러리
  5. Mockito : 테스트에 사용할 가짜 객체인 목 객체를 쉽게 만들고, 관리하고, 검증할 수 있게 지원하는 테스트 케이스
  6. JSONassert : JSON용 어설션 라이브러리
  7. JsonPath : JSON 데이터에서 특정 데이터를 선택하고 검색하기 위한 라이브러리

3. JUnit

JUnit은 자바 언어를 사용하는 소프트웨어의 단위 테스트를 위한 테스팅 프레임워크입니다. 주로 자바 언어로 작성된 코드의 품질을 보장하고 유지보수성을 높이기 위해 사용됩니다. JUnit은 테스트 케이스를 작성하고 실행하는 데 도움이 되는 강력한 도구로 자동화된 테스트 환경을 제공합니다. JUnit의 특징은 다음과 같습니다.

  1. 테스트 방식을 구분할 수 있는 애너테이션을 제공
  2. @Test 애너테이션으로 메서드를 호출할 때마다 새로운 인스턴스를 생성. 독립 테스트가 가능
  3. 예상 결과를 검증하는 어설션 메서드 제공
  4. 사용 방법이 단순하고 테스트 코드 작성 시간이 적음
  5. 자동으로 실행하고, 자체 결과를 확인하고 즉각적인 피드백을 제공

3.1 JUnit 예제1

JUnit으로 단위 테스트 코드를 작성합니다. 다음 예제 코드는 간단한 사칙 연산을 활용한 테스트 코드입니다.

public class JUnitTest {
    @DisplayName("1 + 2는 3이다.") // 테스트 이름
    @Test // 테스트 메서드
    public void junitTest() {
        int a = 1;
        int b = 2;
        int sum = 3;
        
        Assertion.assertEquals(a + b, sum); // 값이 동일한지 확인
    }
}

JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 생성하고 테스트가 종료되면 실행 객체를 삭제합니다. junitTest()메서드에 작성한 테스트 코드는 단순하게 JUnit에서 제공하는 검증 메서드인 assertEquals()a + bsum의 값이 같은지 확인합니다. 따라서 assertEquals() 메서드의 첫 번째 인수는 기대하는 값, 두 번째 인수는 검증할 값을 넣어 줍니다.


3.2 JUnit 생명주기 애너테이션

아래에 @BeforeAll, @BeforeEach, @AfterAll, @AfterEach 애너테이션을 사용하는 간단한 JUnit 테스트 코드 예제를 제시하겠습니다. 이 예제는 간단한 계산기 클래스를 테스트하는 코드입니다.

import org.junit.jupiter.api.*;

public class CalculatorTest {

    @BeforeAll
    static void setUpClass() {
        // BeforeAll : 테스트 클래스 수준에서 한 번만 실행되는 초기화 작업
        System.out.println("Before all tests");
    }

    @BeforeEach
    void setUp() {
        // BeforeEach : 각 테스트 메서드 전에 실행되는 초기화 작업
        System.out.println("Before each test");
    }

    @Test
    void testAddition() {
        int result = Calculator.add(3, 5);
        Assertions.assertEquals(8, result);
    }

    @Test
    void testSubtraction() {
        int result = Calculator.subtract(8, 3);
        Assertions.assertEquals(5, result);
    }

    @AfterEach
    void tearDown() {
        // AfterEach : 각 테스트 메서드 후에 실행되는 정리 작업
        System.out.println("After each test");
    }

    @AfterAll
    static void tearDownClass() {
        // AfterAll : 테스트 클래스 수준에서 한 번만 실행되는 정리 작업
        System.out.println("After all tests");
    }
}

위의 예제에서:

  • @BeforeAll

    • 테스트 클래스 수준에서 한 번만 실행되는 초기화 메서드를 정의합니다.(static 선언 필수)
    • 데이터베이스를 연결해야 하거난 테스트 환경을 초기화할 때 사용합니다.
  • @BeforeEach

    • 각 테스트 메서드 전에 실행되는 초기화 메서드를 정의합니다. (static 선언 불가능)
    • 테스트 메서드에서 사용하는 객체를 초기화하거나 테스트에 필요한 값을 미리 넣을 때 사용할 수 있습니다.
  • @AfterEach

    • 각 테스트 메서드 후에 실행되는 정리 메서드를 정의합니다. (static 선언 불가능)

    • 테스트 이후에 특정 데이터를 삭제해야 하는 경우 사용합니다.

  • @AfterAll

    • 테스트 클래스 수준에서 한 번만 실행되는 정리 메서드를 정의합니다. (static 선언 필수)

    • 데이터베이스 연결을 종료할 때나 공통적으로 사용하는 자원을 해제할 때 사용할 수 있습니다.

  • @Test는 각 테스트 메서드를 정의합니다.

이러한 애너테이션을 사용하면 테스트의 전체 수명 주기 동안 특정한 초기화 및 정리 작업을 수행할 수 있습니다.

image-20240106233649566

3.3 AssertJ로 검증문 가독성 높이기

AssertJ는 JUnit과 함께 사용해 검증문의 가독성을 높여주는 라이브러리입니다. 예를 들어 앞서 작성한 테스트 코드의 Assertion은 기댓값과 실제 비교값을 명시하지 않으므로 비교 대상이 헷갈립니다. 예를 들어 다음 코드를 보면 기댓값과 비교값이 잘 구분되지 않습니다.

Assertion.assertEquals(a + b, sum);

AssertJ를 사용하면 다음과 같습니다.

assertThat(a + b).isEqualTo(sum);

이 경우 a와 b를 더한 값이 sum과 같아야 한다는 의미로 명확하게 읽히기 때문에 코드를 읽는 사람이 헷갈리지 않습니다. AssertJ에는 값이 같은지 비교하는 isEqualTo(), isNotEqualTo() 메서드 말고도 다양한 메서드도 제공합니다. AssertJ에서 제공하는 자주 사용되는 메서드는 다음과 같습니다.

메서드 이름설명
isEqualTo(A)A 값과 같은지 검증
isNotEqualTo(A)A 값과 다른지 검증
contains(A)A 값을 포함하는지 검증
doesNotContain(A)A 값을 포함하지 않는지 검증
startWith(A)접두사가 A인지 검증
endWith(A)접미사가 A인지 검증
isEmpty()비어있는 값인지 검증
isNotEmpty()비어있지 않은 값인지 검증
isPositive()양수인지 검증
isNegative()음수인지 검증
isGreaterThan(1)1보다 큰 값인지 검증
isLessThan(1)1보다 작은 값인지 검증

4. 실전 테스트

4.1 BeforeXXX, AfterXXX 설정하기

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

4.2 테스트 코드 작성

@SpringBootTest // 테스트용 애플리케이션 컨텍스트 개발
@AutoConfigureMockMvc // MockMvc 생성
class TestControllerTest {
    // ... 생략(위의 BeforeXXX, AfterXXX 설정하기 코드) ...
    
    @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) // 1.
            	.accept(MediaType.APPLICATION_JSON)); // 2.
        
        // then
        result
            .andExpect(status().isOk()) // 3.
            // 4. 응답의 0번째 값이 DB에서 저장한 값과 같은지 확인
            .andExpect(josnPath("$[0].id").value(savedMember.getId()))
            .andExpect(jsonPath("$[0].name").value(savedMember.getName()));
    }
}

위의 given-when-then 패턴의 의미는 다음과 같습니다.

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

1. perform()메서드는 요청을 전송하는 역할을 수행하는 메서드입니다. 결과로 ResultActions 객체를 받으며, ResultActions 객체는 반환값을 검증하고 확인하는 andExpect() 메서드를 제공해줍니다. andExpect()에 관한 내용은 3. 에서 정리합니다.

2. accept() 메서드는 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드입니다. JSON, XML 등 다양한 타입이 있지만, 여기에서는 JSON을 받는다고 명시하도록 합니다.

3. andExpect() 메서드는 응답을 검증합니다. TestController에서 만든 API는 응답으로 OK(200)을 반환하므로 이에 해당하는 메서드인 isOk() 메서드를 사용해 응답 코드가 OK(200)인지 확인합니다.

4. jsonPath("$[0].필드명")은 JSON 응답값의 값을 가져오는 역할을 하는 메서드입니다. 0번째 배열에 들어있는 객체의 id, name값을 가져오고, 저장된 값과 같은지 확인합니다.

HTTP 주요 응답 코드는 다음과 같습니다.

코드매핑 메서드설명
200 OKisOk()HTTP 응답 코드가 200 OK인지 검증
201 CreatedisCreated()HTTP 응답 코드가 201 Created인지 검증
400 Bad RequestisBadRequest()HTTP 응답 코드가 400 Bad Request인지 검증
403 ForbiddenisForbidden()HTTP 응답 코드가 403 Forbidden인지 검증
404 Not FoundisNotFound()HTTP 응답 코드가 404 Not Found인지 검증
400 번대 응답 코드is4xxClientError()HTTP 응답 코드가 400 번대 응답 코드인지 검증
500 Interal Server ErrorisInteralServerError()HTTP 응답 코드가 500 Interal Server Error인지 검증
500번대 응답 코드is5xxServerError()HTTP 응답 코드가 500번대 응답 코드인지 검증
profile
필기하고, 타이핑하고, 말하면서 읽고, 코딩하고, 눈으로 읽고 오감으로 공부하기

0개의 댓글