테스트(test)란 프로그램의 품질을 검증하는 것으로, 의도대로 프로그램이 잘 동작하는지 확인하는 과정이다. 테스트 초창기에는 사람이 직접 요청을 보내고 응답을 받아 일일이 확인하는 방식으로 진행했다. 하지만 이제는 테스트 도구를 이용해 반복적인 검증 절차를 자동화할 수 있다. 다양한 문제를 미리 예방하고 코드 변경 등으로 인해 발생하는 부작용도 조기에 발견할 수 있다.
테스트 도구를 활용해 코드를 검증한다는 것은 테스트 코드(test code)를 작성해 실행한다는 말이다. 테스트 코드는 보통 다음 3단계로 작성한다.
작성한 코드가 테스트를 통과하면 지속적인 리팩터링으로 코드를 개선한다. 그러나 테스트를 통과하지 못하면 잘못된 부분을 찾아 고치는 디버깅(debugging)을 해야 한다.
테스트 코드는 다양한 경우를 대비해 작성한다. 이를 테스트 케이스(test case)라고 한다. 테스트 케이스는 성공할 경우뿐만 아니라 실패할 경우도 고려해야 한다. 또한, 성공1, 성공2, ···, 실패1, 실패2, ···와 같이 다양한 상황을 예상해 세부적으로 작성해야 한다.
테스트를 통한 코드 검증과 리팩터링은 개발자의 기본 소양이다. 이를 기반으로 한 개발 방법론인 테스트 주도 개발 또한 개발의 핵심 패러다임으로 자리 잡고 있다. 테스트 주도 개발(TDD, Test Driven Development)이란 일단 테스트 코드를 만든 후 이를 통과하는 최소한의 코드부터 시작해 점진적으로 코드를 개선 및 확장해 나가는 개발 방식이다.
test > java > com.example.firstproject > service > ArticleServiceTest로 테스트 코드를 생성한다.
테스트하고 싶은 메서드에서 마우스 오른쪽 -> Generate -> Test를 통해 바로 생성할 수도 있다.
package com.example.firstproject.service;
import com.example.firstproject.dto.ArticleForm;
import com.example.firstproject.entity.Article;
import org.junit.jupiter.api.Test; // Test 패키지 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*; // 앞으로 사용할 수 있는 패키지 임포트
@SpringBootTest // 해당 클래스를 스프링 부트와 연동해 테스트
class ArticleServiceTest {
@Autowired
ArticleService articleService; // articleService 객체 주입
@Test // 해당 메서드가 테스트 코드임을 선언
void index() {
// 1. 예상 데이터
Article a = new Article(1L, "가가가가", "1111");
Article b = new Article(2L, "나나나나", "2222");
Article c = new Article(3L, "다다다다", "3333");
List<Article> expected = new ArrayList<Article>(Arrays.asList(a, b, c));
// 2. 실제 데이터
List<Article> articles = articleService.index();
// 3. 비교 및 검증
assertEquals(expected.toString(), articles.toString());
}
}
우선 index() 메서드(모든 게시글 조회)를 테스트하는 코드부터 작성했다.


이런식으로 나머지 메서드들의 성공했을 경우와 실패했을 경우의 테스트 코드를 작성한다.
@Test
void show_성공_존재하는_id_입력() {
// 1. 예상 데이터
Long id = 1L;
Article expected = new Article(id, "가가가가", "1111");
// 2. 실제 데이터
Article article = articleService.show(id);
// 3. 비교 및 검증
assertEquals(expected.toString(), article.toString());
}
@Test
void show_실패_존재하지_않는_id_입력() {
// 1. 예상 데이터
Long id = -1L;
Article expected = null;
// 2. 실제 데이터
Article article = articleService.show(id);
// 3. 비교 및 검증
assertEquals(expected, article);
}
@Test
@Transactional
void create_성공_title과_content만_있는_dto_입력() {
// 1. 예상 데이터
String title = "라라라라";
String content = "4444";
ArticleForm dto = new ArticleForm(null, title, content);
Article expected = new Article(4L, title, content);
// 2. 실제 데이터
Article article = articleService.create(dto);
// 3. 비교 및 검증
assertEquals(expected.toString(), article.toString());
}
@Test
void create_실패_id가_포함된_dto_입력() {
// 1. 예상 데이터
Long id = 4L;
String title = "라라라라";
String content = "4444";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = null;
// 2. 실제 데이터
Article article = articleService.create(dto);
// 3. 비교 및 검증
assertEquals(expected, article);
}
@Test
@Transactional
void update_성공_존재하는_id와_title_content가_있는_dto_입력() {
Long id = 1L;
String title = "가나다라";
String content = "1234";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = new Article(id, title, content);
Article article = articleService.update(id, dto);
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_성공_존재하는_id와_title만_있는_dto_입력() {
Long id = 1L;
String title = "1111";
String content = null;
Article expected = new Article(id, title, "1111");
ArticleForm dto = new ArticleForm(id, title, content);
Article article = articleService.update(id, dto);
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_실패_존재하지_않는_id의_dto_입력() {
Long id = 4L;
String title = "1111";
String content = "가가가가";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = null;
Article article = articleService.update(id, dto);
assertEquals(expected, article);
}
@Test
@Transactional
void delete_성공_존재하는_id_입력() {
Long id = 1L;
String title = "가가가가";
String content = "1111";
Article expected = new Article(id, title, content);
Article article = articleService.delete(id);
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void delete_실패_존재하지_않는_id_입력() {
Long id = 4L;
Article expected = null;
Article article = articleService.delete(id);
assertEquals(expected, article);
}
중간중간에 @Transactional 어노테이션을 추가했는데, 이유는 전체 테스트를 돌릴 경우, 데이터에 변경을 가하는 테스트에서 생성, 수정, 삭제가 이루어지고 그 상태로 다른 테스트가 진행되면 테스트 실패가 이루어질 수 있기 때문에 테스트가 완료되면 원래대로 롤백되도록 하기 위해서이다.