스프링 부트 3와 테스트 for 백엔드 개발자

이 글은 [스프링 부트 3 백엔드 개발자 되기(자바 편)]에서 발췌했습니다.
골든래빗 출판사

테스트 코드는 작성한 코드가 의도대로 잘 동작하고 예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드입니다. 보통 테스트 코드 관련 공부는 본 개발 공부를 하느라 미루는 경우가 많습니다. 하지만 필자는 유지보수에도 매우 좋고, 코드 수정 시 기존 기능이 제대로 작동하지 않을까 봐 걱정하지 않아도 된다는 장점이 있는 테스트 코드 공부를 꼭 추천합니다.

 

1. 테스트 코드란?

테스트 코드는 test 디렉터리에서 작업합니다. 우리의 프로젝트에도 이미 test 디렉터리가 있습니다.

테스트 코드에도 다양한 패턴이 있습니다. 그중 제가 사용할 패턴은 given-when-then 패턴인데요, 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);
}

코드를 보면 세 부분으로 나누어져 있습니다. 메뉴를 저장하기 위해 준비하는 과정인 given절, 실제로 메뉴를 저장하는 when절, 메뉴가 잘 추가되었는지 검증하는 then절로 나누어져 있습니다. 우선은 이 정도로 테스트 코드가 무엇인지 감만 잡아두겠습니다. 본격적인 내용은 바로 다음 실습을 통해 공부해보죠!

 

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

스프링 부트는 애플리케이션을 테스트하기 위한 도구와 애너테이션을 제공합니다. springboot- starter-test 스타터에 테스트를 위한 도구가 모여 있습니다.

▼ 스프링 부트 스타터 테스트 목록

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

이 중에서 JUnit과 AssertJ를 가장 많이 사용하죠. 여기서는 이 두 도구를 자세히 알아보겠습니다.

 

3. JUnit이란?

JUnit은 자바 언어를 위한 단위 테스트 프레임워크입니다. 단위 테스트라는 말이 생소할 텐데요. 단위 테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미합니다. 이때 단위는 보통 메서드가 됩니다. JUnit을 사용하면 단위 테스트를 작성하고 테스트하는 데 도움을 줍니다. 사용법도 간단해 쉽게 익힐 수 있습니다. 무엇보다 테스트 결과가 직관적이라 좋죠. 구체적인 JUnit의 특징은 다음과 같습니다.

▼ Junit의 특징

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

 

4. JUnit으로 단위 테스트 코드 만들기

이제 JUnit을 실제로 사용해봅시다.

[01단계 To do] JUnitTest 파일을 만들겠습니다. [src → test → java] 폴더에 JUnitTest.java 파 일을 생성하고 코드를 따라 작성해보세요.

 

▼ JUnitTest.java

public class JUnitTest {
    @DisplayName("1 + 2는 3이다") // 테스트 이름
    @Test // 테스트 메서드
    public void junitTest() {
        int a = 1;
        int b = 2;
        int sum = 3;

        Assertions.assertEquals(sum, a + b); // 값이 같은지 확인
    }
}

@DisplayName 애너테이션은 테스트 이름을 명시합니다. @Test 애너테이션을 붙인 메서드는 테스트를 수행하는 메서드가 됩니다. JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고 테스트가 종료되면 실행 객체를 삭제합니다. junitTest( ) 메서드에 작성한 테스트 코드 설명을 하자면 매우 간단합니다. 이 테스트에서는 JUnit에서 제공하는 검증 메서드인 assertEquals( )로 a + b와 sum의 값이 같은지 확인합니다. 이를 통해 assertEquals( ) 메서드의 사용법을 자연스럽게 알았을 겁니다. assertEquals( ) 메서드의 첫 번째 인수에는 기대하는 값, 두 번째 인수에는 실제로 검증할 값을 넣어줍니다.

[02단계] 실제로 테스트 코드가 잘 동작하는지 확인해보겠습니다. 화면을 참고해 [▶] 버튼을 클릭 하고 [Run ‘JUnitTest’]를 클릭해서 테스트를 실행해보세요.

 

테스트가 끝나면 콘솔창에 테스트 결과가 출력됩니다. 단, 그림처럼 성공 여부, 테스트 케이스의 이름, 테스트 실행 시간 정보를 확인하기 위해서는 [체크] 버튼을 눌러야 합니다.

 

[03단계] 만약 테스트가 실패하면 어떻게 될까요? 실패를 위한 테스트 케이스를 하나 더 추가해봅시다. junitTest( ) 메서드 바로 아래에 다음 코드를 추가해보세요.

 

▼ JUnitTest.java

public class JUnitTest {
    ... 생략 ...
    @DisplayName("1 + 3는 4이다.")
    @Test
    public void junitFailedTest() {
        int a = 1;
        int b = 3;
        int sum = 3;

    Assertions.assertEquals(sum, a + b); // 실패하는 케이스
    }
}

실패용 테스트 케이스를 실행하면 테스트가 실패했다는 표시와 함께 기댓값과 실제로 받은 값을 비교해서 알려줍니다. 이렇게 JUnit은 테스트 케이스가 하나라도 실패하면 전체 테스트를 실패한 것으로 보여줍니다.

[04단계] 조금 더 알아볼까요? 이번에는 자주 사용하는 JUnit의 애너테이션을 알아보겠습니다. junitFailedTest( ) 메서드는 삭제하세요. 그리고 JUnitCycleTest.java 파일을 만들어 코드를 입력하세요. 이번에는 테스트용 메서드가 많죠? 앞서 JUnit은 각 테스트에 대해 객체를 만들어 독립적으로 실행한다고 했는데 드디어 그 내용을 확인해볼 수 있겠네요. 또 테스트는 애너테이션에 따라 실행 순서가 정해집니다. 이번 실습을 통해 확인해보기 바랍니다.

 

▼ JUnitCycleTest.java

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이 아니어야 합니다.

애너테이션을 중심으로 JUnit의 실행 흐름을 살펴보면 다음과 같습니다. @BeforeEach부터 @ AfterEach까지 테스트 개수만큼 반복된 결과물을 볼 수 있겠네요.

 

[05단계] 정말 그런지 테스트 코드를 실행해서 출력 결과를 살펴봅시다.

결과를 보면 @BeforeAll 애너테이션으로 설정한 메서드가 실행되고, 그 이후에는 테스트 케이스 개수만큼 @BeforeEach → @Test → @AfterEach의 생명주기로 테스트가 진행됩니다. 모든 테스트 케이스가 끝나면 @AfterAll 애너테이션으로 설정한 메서드를 실행하고 종료하네요. JUnit의 사용 방법은 이쯤해 마치겠습니다. 이번에는 JUnit과 함께 사용하면 정말 좋은 AssertJ로 넘어가보죠!

 

5. AssertJ로 검증문 가독성 높이기

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

 

▼ 기댓값과 비교값이 잘 구분되지 않는 Assertion 예

Assertions.assertEquals(sum, a + b);

큰 문제라고 생각하지 않을 수 있겠지만 대규모 프로젝트에서는 조금 더 명확한 모습의 코드가 실수를 줄일 수 있어 이런 가독성은 꽤 중요한 문제입니다. 하지만 이럴 때 AssertJ를 사용하면 어떨까요? 다음은 AssertJ를 적용한 코드입니다.

▼ 가독성이 좋은 AssertJ 예

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

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

책 내용 중 궁금한 점, 공부하다가 막힌 문제 등 개발 관련 질문이 있으시다면

언제나 열려있는 <스프링 부트 3 백엔드 개발자 되기> 저자님의

카카오채널로 질문해주세요!

신선영

리멤버 백엔드 개발자. 하드 스킬과 소프트 스킬 역량을 강화하고자 부단히 공부하고 글로 남기는 백엔드 개발자입니다. 평일 기준 하루 평균 600뷰의 기술 블로그를 운영하고, 모교 학생을 대상으로 정기 세미나와 멘토링을 진행합니다. 구독자가 1,000명 정도 되는 사이드 프로젝트를 기획하고 개발하고 운영한 경험이 있습니다.

저자 블로그 shinsunyoung.tistory.com
저자 깃허브 github.com/shinsunyoung

 

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내