테스트 코드란 소프트웨어 개발 과정에서 코드가 예상대로 작동하는지 확인하기 위해 작성하는 코드다.
특히 백엔드 개발에서는 서버, 데이터베이스 등 다양한 요소와 상호작용하기 때문에, 테스트 코드 작성은 매우 중요하다.
1. 테스트 코드
1. 테스트 코드의 중요성
- 버그 발견 용이: 코드 변동 시 실시간으로 버그를 찾아낼 수 있다.
- 리팩토링 용이: 기능 변경이나 코드 구조 변경 시 기존 기능이 올바르게 작동하는지 확인할 수 있다.
- 개발 속도 향상: 오류를 빠르게 찾아 해결함으로써 개발 속도가 향상된다.
2. 테스트 코드 작성 시 주의 사항
- 독립성 유지: 각 테스트는 다른 테스트와 독립적으로 실행되어야 한다.
- 명확한 목적: 한 번에 하나의 기능만을 테스트해야 한다.
- 자동화: 모든 테스트는 수동 개입 없이 자동으로 실행될 수 있어야 한다.
2. JUnit
JUnit은 단위 테스트(작은 단위, 보통 메소드 단위로 검증)를 쉽고 효율적으로 작성할 수 있도록 도와준다.
- 단순성: 사용법이 간단하여 배우기 쉽고 적용하기 편리하다.
- 자동화 테스트 지원: 반복적인 테스트 작업을 자동화하여 개발자의 시간과 노력을 절약한다.
- 다양한 어노테이션 제공: 테스트 케이스를 구성하고 실행 순서를 정하는 등 다양한 기능을 제공한다.
1. 기본 테스트 코드
JUnit을 사용하여 간단한 테스트 코드를 작성해보자.
예시 코드: 간단한 덧셈 함수 테스트
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JUnitTest {
@DisplayName("1 + 2는 3이다")
@Test
public void addIntTest() {
int a = 1;
int b = 2;
int sum = 3;
assertEquals(sum, a + b);
}
}
- @DisplayName : 테스트의 이름을 지정한다.
- @Test: 해당 메소드가 테스트 메소드임을 나타내는 어노테이션이다.
- assertEquals(sum, a + b);: 첫 번째 인자(기대하는 값)와 두 번째 인자(실제 값)이 같은지 비교한다. 이 경우, a+b가 3과 같은지 테스트한다.
테스트 성공시 아래 이미지와 같이 체크✅ 표시가 된다.
만약 테스트가 실패하면 실패❌로 표시되고, 콘솔창의 로그로 결과를 확인할 수 있게 된다.
위의 테스트 코드에서 sum을 4로 바꾸면 아래와 같은 결과가 출력된다.
2. 주요 어노테이션
JUnit에서 주로 사용하는 어노테이션은 다음과 같다.
- @Test : 테스트 메소드임을 표시한다.
- @Before: 각 테스트 실행 전에 실행될 메소드를 지정한다. 각 테스트 실행 전에 객체를 초기화하거나 값을 미리 넣을 때 사용한다.
- @After: 각 테스트 실행 후에 실행될 메소드를 지정한다. 테스트 이후 특정 데이터를 삭제하는 경우 사용한다.
- @BeforeAll, @AfterAll: 모든 테스트 전후에 한 번씩만 실행될 메소드를 지정하며, static으로 선언한다. 예를 들어 데이터베이스를 연결/종료하는 메소드를 작성할 수 있다.
아래는 주요 어노테이션을 사용한 예시이다.
JUnit 주요 어노테이션 사용
public class JUnitTest {
@BeforeAll
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 실행");
}
@AfterEach
public void afterEach() {
System.out.println("afterEach 실행");
}
@AfterAll
static void afterAll() {
System.out.println("afterAll 실행");
}
}
실행시 콘솔창의 결과는 아래와 같다.
beforeAll과 afterAll은 가장 첫번째와 마지막에 한번씩, beforeEach와 afterEach는 각 테스트 전후로 반복 실행되는 것을 확인할 수 있다.
3. AssertJ
AssertJ는 Java용 풍부한 플루언트(fluent) 어서션(assertion) 라이브러리로, JUnit과 같은 테스팅 프레임워크와 함께 사용하여 테스트 코드의 가독성과 작성 편의성을 대폭 향상시킨다.
이 라이브러리는 자바 개발자가 보다 자연어에 가까운 코드로 테스트 어서션을 작성할 수 있게 해준다.
1. AssertJ의 특징
- 풍부한 플루언트 API: AssertJ는 풍부한 플루언트 API를 제공하여, 개발자가 의도를 명확하게 표현할 수 있도록 도와준다.
- 자동완성 친화적: IDE에서 제공하는 자동완성 기능과 잘 어울려 테스트 코드 작성 속도를 높여준다.
- 커스텀 어서션: 개발자가 자신만의 어서션을 만들어 사용할 수 있는 기능을 제공한다.
- 다양한 어서션: 객체, 배열, 날짜, 파일 등 다양한 타입에 대한 어서션을 지원한다.
- 예외 검증: 예외 발생 여부를 쉽게 검증할 수 있다.
2. AssertJ 사용
위의 JUnit에서 사용한 예시코드의 검증 부분을 AssertJ를 적용해보자.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class JUnitTest {
@DisplayName("1 + 2는 3이다")
@Test
public void addIntTest() {
int a = 1;
int b = 2;
int sum = 3;
//assertEquals(sum, a + b);
assertThat(a + b).isEqualTo(sum);
}
}
- 기존 형태 : 같은지 검증( 기댓값 , 실제값 );
- AssertJ 사용 형태 : 검증( 실제값 ).같다( 기댓값 );
바꾼 형태는 기존보다 자연스럽게 읽히고 기댓값과 실제값을 명확히 확인할 수 있다.
예시에서 사용한 '같다'의 isEqualTo 대신 다양한 메소드를 활용하여 테스트의 결과를 검증할 수 있다.
AssertJ의 검증 메소드
메소드 이름 | 설명 |
isEqualTo(A) | A 값과 같다 |
isNotEqualTo(A) | A 값과 다르다 |
contains(A) | A 값을 포함한다 |
doesNotContain(A) | A 값을 포함하지 않는다 |
startsWith(A) | A로 시작한다 (접두사가 A이다) |
endsWith(A) | A로 끝난다 (접미사가 A이다) |
isNull() | null이다 |
isNotNull() | null이 아니다 |
isEmpty() | 값이 비어 있다 |
isNotEmpty() | 값이 비어 있지 않다 |
isPositive() | 양수이다 |
isNegative() | 음수이다 |
isZero() | 값이 0이다 |
isGreaterThan(1) | 1보다 큰 값이다 |
isLessThan(1) | 1보다 작은 값이다 |
4. given-when-then 패턴
테스트 코드를 작성할 때 널리 사용되는 방법론 중 하나는 'given-when-then' 패턴이다.
이 패턴은 테스트를 세 부분으로 나누어 명확한 구조를 제공한다.
- Given: 테스트의 전제 조건을 설정한다. 테스트를 실행하기 전의 상태를 정의한다.
- When: 테스트할 행동 또는 메소드를 실행한다.
- Then: 테스트의 결과를 검증한다. 예상한 결과가 나왔는지 확인한다.
이 패턴을 사용함으로써, 테스트 코드의 가독성과 유지보수성이 크게 향상된다.
예시 코드: given-when-then 패턴 사용
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class JUnitTest {
@DisplayName("1 + 2는 3이다")
@Test
public void addIntTest() {
// Given
int a = 1;
int b = 2;
// When
int result = a + b;
// Then
assertThat(result).isEqualTo(3);
}
}
위 코드에서는 두 숫자의 합을 계산하는 add 메소드를 테스트한다.
- Given에서는 테스트에 사용될 두 숫자를 정의한다.
- When에서는 두 숫자의 합을 계산한다.
- Then에서는 assertThat( 실제값 ).isEqualTo( 기댓값 ) 메소드를 사용하여 계산 결과가 기대한 값(3)과 일치하는지 검증한다.
5. 모의 객체(Mock Object)
모의 객체(Mock Object)는 실제 객체를 대신하여 테스트 환경에서 사용되는 객체를 의미한다.
이는 주로 실제 객체의 행동을 모방하며, 테스트를 위해 특정한 상태나 값, 행동을 미리 설정할 수 있다.
복잡한 의존성이나 외부 시스템과의 연동, 데이터베이스 접근 등을 필요로 하는 경우, 실제 환경을 구성하는 것이 어렵거나 불가능할 때 모의 객체를 사용하게 된다.
1. 모의 객체의 사용 목적
- 테스트의 단순화 및 가속화: 실제 객체 대신 모의 객체를 사용하면 복잡한 설정이나 초기화 과정 없이 테스트를 빠르게 진행할 수 있다.
- 테스트의 독립성 보장: 외부 시스템, 데이터베이스 등에 의존하지 않아, 테스트가 다른 환경에 의해 영향을 받지 않는다.
- 예측 불가능한 상황의 테스트: 네트워크 오류, 데이터베이스 연결 실패 등 실제 환경에서 발생할 수 있는 여러 예외 상황들을 모의 객체를 통해 재현하고 테스트할 수 있다.
2. 모의 객체 사용 방법
Java에서 모의 객체를 생성하고 사용하기 위해 주로 사용되는 라이브러리는 Mockito이다.
아래는 Mockito를 사용하여 모의 객체를 생성하고, 기본적인 사용 방법을 설명하는 예시 코드이다.
Mockito를 이용한 모의 객체 생성
import static org.mockito.Mockito.*;
// 모의 객체 생성
List mockedList = mock(List.class);
// 모의 객체에 대한 행동 설정
when(mockedList.get(0)).thenReturn("첫 번째 요소");
// 모의 객체 사용
System.out.println(mockedList.get(0));
// 검증
verify(mockedList).get(0);
- 모의 객체 생성: mock(List.class)를 통해 List 인터페이스의 모의 객체를 생성한다.
- 행동 설정: when(mockedList.get(0)).thenReturn("첫 번째 요소");를 통해 모의 객체가 특정 메소드(get(0))를 호출할 때 반환할 값을 설정한다.
- 모의 객체 사용: 실제 코드에서 모의 객체를 사용한다. 여기서는 mockedList.get(0)을 호출하면, 위에서 설정한 대로 "첫 번째 요소"라는 문자열이 반환된다.
- 검증: verify(mockedList).get(0);를 통해 get(0) 메소드가 실제로 호출되었는지를 검증한다.
'Java > Spring Framework' 카테고리의 다른 글
[Spring Boot] IntelliJ에서 스프링 부트 프로젝트 시작하기 (0) | 2024.05.03 |
---|---|
[Spring] 테스트 환경에서 별도의 데이터베이스 연결하기 (0) | 2024.04.21 |
[Spring Boot] 스프링 시큐리티 + JWT (0) | 2024.04.01 |
[Spring Boot + React] 리액트를 위한 스프링 시큐리티 추가 설정 (CORS 오류 해결+인증 성공시 응답) (0) | 2024.03.20 |
[Spring Boot] 스프링 시큐리티로 회원가입, 로그인, 로그아웃 구현하기 (0) | 2024.03.17 |