1. 단위 테스트
하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트이다. 통합 테스트의 경우 시스템을 구성하는 컴포넌트들이 커질수록 테스트 시간이 길어지지만, 단위 테스트의 경우 해당 부분만 독립적으로 테스트하기에 코드의 변경이 있어도 빠르게 문제 여부를 확인할 수 있다. CleanCode 책에 의하면 깨끗한 테스트 코드는 다음 5가지 규칙을 따라야 한다.
Fast - 빠르게 동작하여 자주 돌릴 수 있어야 한다.
Independent - 테스트는 독립적이며 서로 의존해서는 안된다.
Repeatable - 어느 환경에서도 반복이 가능해야 한다.
Self-validating - 테스트는 성공 또는 실패로 결과를 내어 자체 검증되어야 한다.
Timely - 테스트는 적시에, 테스트하려는 실제코드를 구현하기 직전에 구현해야 한다.
2. Junit
Junit은 단위 테스트를 지원하는 오픈소스 프레임워크로 다음과 같은 특징을 가진다.
- 문자 혹은 GUI 기반으로 실행
- @Test 메서드를 호출할 때마다 새 인스턴스 생성
- 예상결과를 검증하는 assertion 제공
- 자동실행, 자체결과 확인 및 즉각적인 피드백 제공
- 테스트 방식을 구분할 수 있는 어노테이션을 제공하며, 어노테이션만으로 간결하게 실행이 가능
▶ 2-1. 어노테이션 종류
@DisplayName - 테스트 이름 명시
@Test - 테스트를 수행할 메서드, Junit은 각 테스트끼리 영향을 주지 않도록 테스트 실행 객체를 매 테스트마다 만들고 종료 시 삭제
@BeforeAll - 전체 테스트를 시작하기 전에 1회 실행 (ex. 데이터베이스 연결, 테스트환경 초기화, 전체 테스트 실행주기에 한 번만 호출)
@BeforeEach - 테스트 케이스를 시작하기 전마다 실행 (테스트 메서드에 사용하는 객체 초기화, 테스트에 필요한 데이터 삽입 등)
@AfterAll - 전체 테스트를 마치고 종료하기 전에 1회 실행 (데이터베이스 연결 종료, 공통으로 사용하는 자원 해제 등)
@AfterEach - 테스트 케이스를 종료하기 전마다 실행 (테스트 이후 특정데이터를 삭제 등)
▶ 2-2. AssertJ
Junit과 사용해 가독성을 높여주는 라이브러리로 다양한 문법을 지원한다. 기존 Junit은 기댓값과 실제 비교대상이 확실히 보이지 않아 잘 구분이 안되지만 isEqualTo 등 명확한 의미의 매머드로 대체가 가능하다.
▶ 2-3. given-when-then 패턴
요즘 단위테스트의 가장 보편적인 형태로 1개의 단위테스트를 3단계로 나눠서 처리하는 패턴이다.
- given = 테스트 실행을 준비하는 단계 (어떤 상황, 데이터가 주어졌을 때)
- when = 테스트를 진행하는 단계 (어떤 함수를 실행시키면 )
- then = 테스트 결과를 검증하는 단계 (어떤 결과가 기대된다.)
3. 단위 테스트 예제
점수의 평균을 계산해주는 클래스에 대한 단위 테스트를 해보자. 해당 예제는 객체 간의 메시지 교환이 없는 단순한 값 비교, 예외 확인을 위한 테스트 케이스이다.(일반적으로 스프링 애플리케이션은 다양한 객체에서 메시지를 전달받아 의존성이 생기는데, 이럴 경우 Mock(가짜) 객체를 사용하여 테스트가 가능하다.)
▶ 3-0. 테스트 대상인 평균점수 조회
다음과 같이 0점 이상의 점수들에 대한 평균을 구하는 클래스가 있을 때
public class AverageScoreCalculator {
private static Integer sum = 0;
private static Integer count = 0;
public void addScore(Integer score) {
if (!validateScores(score)) {
throw new IllegalStateException("Invalid score");
}
sum += score;
count++;
}
private boolean validateScores(Integer score) {
return score > 0;
}
public Double getAverageScore() {
return (double) (sum / count);
}
}
▶ 3-1. 점수의 평균이 일치하는지 테스트
실제 평균의 값과, 클래스 연산 결과가 일치하는지 테스트한다.
@DisplayName("점수의 평균 테스트")
@Test
void averageScoreTest() {
//given
AverageScoreCalculator averageScoreCalculator = new AverageScoreCalculator();
int[] scores = {10,20,30,40,50};
//when
for (int i = 0; i<scores.length; i++) {
averageScoreCalculator.addScore(scores[i]);
}
Double averageScore = averageScoreCalculator.getAverageScore();
//then
assertThat(averageScore).isEqualTo(Arrays.stream(scores).average().getAsDouble());
}
▶ 3-2. 평균점수의 범위 테스트
점수의 평균이 1~100점 이내에 존재하는지 확인한다.
@DisplayName("평균 점수 범위 테스트")
@Test
void averageScoreRangeTest() {
//given
AverageScoreCalculator averageScoreCalculator = new AverageScoreCalculator();
int[] scores = {10,20,30,40,50};
//when
for (int i = 0; i<scores.length; i++) {
averageScoreCalculator.addScore(scores[i]);
}
Double averageScore = averageScoreCalculator.getAverageScore();
//then
assertThat(averageScore >= 0 && averageScore <= 100).isTrue();
}
▶ 3-3. 개별점수 유효성 테스트
유효하지 않은 점수가 인풋 될 경우 IllegalStateException이 기대되기에, assertThrow로 Exception을 테스트한다.
@DisplayName("개별 잘못된 점수 테스트")
@Test
public void averageScoreInvalidScoreTest(){
//given
AverageScoreCalculator averageScoreCalculator = new AverageScoreCalculator();
int[] scores = {10,20,30,40,-1};
//when
final IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
for (int i = 0; i<scores.length; i++) {
averageScoreCalculator.addScore(scores[i]);
}
});
//then
assertThat(exception.getMessage()).isEqualTo("Invalid score");
}
4. 주요 Assert 메서드
▶ 4-1. 주요 비교 검증 테스트 메서드
메서드 이름 | 설명 |
isEqualTo(A) | A 값과 같은지 검증 |
isNotEqualTo(A) | A 값과 다른지 검증 |
contains(A) | A 값을 포함하는지 검증 |
doesNotContain(A) | A 값을 포함하지 않는지 검증 |
startWith(A) | 접두사가 A인지 검증 |
endsWith(A) | 접미사가 A인지 검증 |
isEmpty() | 비어 있는 값인지 검증 |
isNotEmpty() | 비어 있지 않은 값인지 검증 |
isPositive() | 양수인지 검증 |
isNegative() | 음수인지 검증 |
isGreaterThan(a) | a보다 큰 값인지 검증 |
isLessThan(a) | a보다 작은 값인지 검증 |
▶ 4-2. HTTP 주요 응답코드 테스트 메서드
코드 | 매핑 메서드 | 설명 |
200 OK | isOk() | HTTP 응답코드가 200 OK인지 검증 |
201 Created | isCreated() | HTTP 응답코드가 201 Created 검증 |
400 Bad Request | isBadRequest() | HTTP 응답코드가 400 BadRequest검증 |
403 Forbidden | isForbidden() | HTTP 응답코드가 403 Forbidden검증 |
404 Not Found | isNotFound() | HTTP 응답코드가 404 Not Found 검증 |
4** | is4xxClientError() | HTTP 응답코드가 4** 검증 |
500 Internal Server Error | isInternalServerError() | HTTP 응답코드가 500 InternalServerError 검증 |
5** | is5xxClientError() | HTTP 응답코드가 5** 검증 |
참고
도서 : 스프링 부트 3 백엔드 개발자 되기 - 자바 편
'Spring' 카테고리의 다른 글
[Spring] Spring Security6 filterchain 사용시 jsp 뷰 렌더링 설정 (0) | 2023.12.18 |
---|---|
[Spring] 순환참조란? The dependencies of some of the beans in the application context form a cycle (0) | 2023.11.17 |
[Spring] 스프링 컨테이너(Spring container)의 개념 (1) | 2023.11.07 |
[Spring] IoC(제어의 역전) & DI(의존성 주입)의 개념 (0) | 2023.11.06 |
[Spring] 스프링부트 + Mybatis 데이터소스 여러개 연결 (스프링 다중 데이터베이스 연결) (0) | 2023.10.20 |