FrameWork/Spring Boot
스프링부트 3 테스트 코드 알아보기 (feat. JUnit, AssertJ)
soooy0
2025. 2. 27. 17:43
테스트 코드란?
- 테스트 코드는 작성한 코드가 의도대로 잘 동작하고 예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드이다.
- 테스트 코드는 test 디렉토리에서 작업한다. (이전 글에 이미 디렉토리를 생성했다.)
- 테스트 코드에도 다양한 패턴이 있는데, 그중 책에서 사용할 패턴은 given-when-then 패턴이다.
given-when-then 패턴
- given: 테스트 실행을 준비하는 단계
- when: 테스트를 진행하는 단계
- then: 테스트 결과를 검증하는 단계
스프링부트 3에서 테스트 진행하기
스프링 부트는 애플리케이션을 테스트하기 위한 도구와 애너테이션을 제공한다.
이전 글을 보면 나는 이미 spring-boot-starter-test 스타터에 테스트를 위한 도구를 사용하도록 설정해두었다.
- JUnit: 자바 프로그래밍 언어용 단위 테스트 프레임워크
- Spring Test & Spring Boot Test : 스프링 부트 애플리케이션을 위한 통합 테스트 지원
- AssertJ: 검증문인 어설션을 작성하는데 사용되는 라이브러리
JUnit이란?
- 자바 언어를 위한 단위 테스트 프레임워크
- 그럼 단위테스트란? 작성한 코드가 의도대로 작성되는지 작은 단위로 검증하는 것을 의미한다. 이때, 단위는 보통 메서드가 된다.
- JUnit은 이러한 단위테스트를 작성하고 테스트 하는데에 도움을 주며, 테스트 결과가 직관적이라는 점이 있다.
- 테스트 방식을 구분할 수 있는 애너테이션을 제공한다.
- @Test 애너테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트 가능
- 예상 결과를 검증하는 어설션 메서드를 제공한다.
- 사용방법이 단순하고 테스트 코드 작성 시간이 적다.
- 자동 실행하며 자체 결과를 확인하고 즉각적인 피드백을 제공한다.
JUnit으로 단위 테스트 코드 만들기
1. src -> test -> java 폴터에 JUnitTest.java 파일 생성하여 코드 작성
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class JUnitTest {
@DisplayName("1 + 2는 3이다.") // test 이름 명시
@Test // test 애너테이션을 붙인 메서드는 테스트를 수행하는 메서드
public void junitTest(){
int a = 1;
int b = 2;
int sum = 3;
// JUnit에서 제공하는 검증 메서드, assertEquals()를 사용
// sum과 a + b의 결과(값)가 같은지 확인, 첫번쨰 인수: 기대하는 값, 두번쨰 인수: 실제 검증할 값
Assertions.assertEquals(sum, a + b);
}
}
- JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고 테스트가 종료되면 실행 객체를 삭제한다.
2. 실제로 코드가 잘 작동하는지 확인해보자. JUnitTest 파일을 우클릭하여 테스트를 실행해보자.
- 체크표시를 눌러서 성공여부 등의 정보를 확인한다. 정상적으로 잘 작동된 모습이다.
3. 만약, 테스트가 실패한다면 어떻게 될까? 실패를 위한 테스트 케이스를 junitTest()메서드 아래에 추가해보자.
// 실패하는 테스트 코드
@DisplayName("1 + 3는 4이다.") // test 이름 명시
@Test // test 애너테이션을 붙인 메서드는 테스트를 수행하는 메서드
public void junitFailedTest(){
int a = 1;
int b = 3;
int sum = 3;
// JUnit에서 제공하는 검증 메서드, assertEquals()를 사용
// sum과 a + b의 결과(값)가 같은지 확인, 첫번쨰 인수: 기대하는 값, 두번쨰 인수: 실제 검증할 값
Assertions.assertEquals(sum, a + b);
}
}
- 실패와 함께 기댓값과 실제로 받은 값을 비교해서 알려준다.
- JUnit은 테스트 케이스가 하나라도 실패하면 전체 테스트를 실패한 것으로 보여준다.
JUnit의 애너테이션
1. JUnitCycleTest.java 파일 생성 후 코드 작성
- JUnit은 각 테스트에 대해 객체를 만들어 독립적으로 실행한다고 했는데, 이 내용을 확인해보도록 하자.
- 테스트는 애너테이션에 따라 실행 순서가 정해진다. 이것도 확인해보자.
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이면 안된다.
@Test (테스트 실행)
@AfterEach (메서드 레벨 정리)
- 각 테스트 케이스를 종료하기 전 매번 실행한다.
- 테스트 이후에 특정 데이터를 삭제해야하는 경우 사용한다.
- 각 인스턴스에 대해 메서드를 호출해야 하므로 메서드는 static이면 안된다.
@AfterAll (클래스 레벨 정리)
- 전체 테스트를 마치고 종료하기 전 한번만 실행한다.
- 데이터베이스 연결을 종료할 때나 공통적으로 사용하는 자원을 해제할 때 사용할 수 있다.
- 전체 테스트 실행 주기에서 한 번만 호출되어야 하기 때문에 메서드를 static으로 선언한다.
즉, @BeforeEach부터 -> @Test -> @AfterEach까지 테스트 개수만큼 반복된 결과물을 볼 수 있다.
즉, @BeforeAll 애너테이션으로 설정한 메서드가 실행되고
이후에는 테스트 케이스 개수만큼 @BeforeEach -> @Test -> @AfterEach 의 생명주기로 테스트가 진행된다.
모든 케이스가 끝나면 @AfterAll로 설정한 메서드를 실행하고 종료한다.
AssertJ
- AssertJ는 JUnit과 함께 사용하여 검증문의 가독성을 높여주는 라이브러리이다. 이를테면 앞서 작성한 테스트 코드의 Assertion은 기댓값과 실제 비교값을 명시하지 않으므로 비교대상이 헷갈린다.
ex) 기댓값과 비교값이 잘 구분되지 않는 예
Assertions.assertEquals(sum, a + b);
이럴 경우, AssertJ를 사용하여 가독성을 높여주는 것이다.
ex) 가독성이 좋은 예
assertThat(a + b).isEqualTo(sum);
- 이렇게 작성하면 a + b 한 값이 sum과 같아야 한다는 의미로 정확히 읽히기 때문에 코드를 읽는 사람이 헷갈리지 않는다.
Quiz
1.
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class JUnitQuiz {
@Test
public void junitTest(){
String name1 = "홍길동";
String name2 = "홍길동";
String name3 = "홍길은";
// 모든 변수가 null이 아닌지 확인
assertThat(name1).isNotNull();
assertThat(name2).isNotNull();
assertThat(name2).isNotNull();
// name1과 name2가 같은지 확인
assertThat(name1).isEqualTo(name2);
// name1과 name3가 다른지 확인
assertThat(name1).isNotEqualTo(name3);
}
@Test
public void junitTest2(){
int num1 = 15;
int num2 = 0;
int num3 = -5;
// num1은 양수인지 확인
assertThat(num1).isPositive();
// num2은 0인지 확인
assertThat(num2).isZero();
// num3은 음수인지 확인
assertThat(num3).isNegative();
// num1 num2보다 큰지 확인
assertThat(num1).isGreaterThan(num2);
// num3 num2보다 작은지 확인
assertThat(num3).isLessThan(num2);
}
}
2.
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class JUnitCycleQuiz {
// 각 테스트를 시작하기 전에 "Hello!"를 출력하는 메서드와 모든 테스트를 끝마치고 "Bye!"를 출력하는 메서드를 추가해야 함.
@BeforeEach
public void beforeEach(){
System.out.println("Hello!");
}
@AfterAll
public static void afterAll(){
System.out.println("Bye!");
}
@Test
public void junitQuiz3(){
System.out.println("This is first test");
}
@Test
public void junitQuiz4(){
System.out.println("This is second test");
}
}