코드 수정 시 문제가 발생하므로 서비스 계층 동작 검증을 자동화 하기 위한 테스트 코드 작성법
13.1 테스트란
프로그램의 품질을 검증하는 것으로, 의도대로 프로그램이 잘 동작하는지 확인하는 과정
테스트 도구 활용해 코드를 검증하기 = 테스트 코드(test code) 작성해 실행하기
📍테스트 코드 작성법
- 예상 데이터 작성하기
- 실제 데이터 획득하기
- 예상 데이터와 실제 데이터 비교해 검증하기
✅ 작성한 코드가 테스트 통과 시 지속적인 리팩토링으로 코드 개선
❎ 테스트 통과 못하면 디버깅(debugging)해서 잘못된 부분을 고치기
테스트 코드로 다양한 테스트 케이스를 작성
- 성공하는 경우와 실패하는 경우 모두 고려
- 성공1, 성공2,,, 실패1, 실패2,,, 와 같이 다양한 상황을 예상해 세부적으로 작성
테스트 주도 개발(TDD, Test Driven Development)
테스트 코드 만들고 통과하는 최소한의 코드부터 시작해 점진적으로 코드를 개선 및 확장해 나가는 개발 방식
13.2 테스트 코드 작성하기
13.2.1 테스트 코드 기본 틀 만들기
ArticleServiceTest 클래스 위에 @SpringBootTest 붙여 스프링 부트와 연동해 통합 테스트를 수행하겠다고 선언
➡️ 테스트 코드에서 스프링 부트가 관리하는 다양한 객체 주입받을 수 있음
@SpringBootTest // 스프링 부트와 연동해 통합 테스트 수행하겠다고 선언
class ArticleServiceTest {
@Autowired // 외부 객체 주입해야 하니 꼭 붙이기!!
ArticleService articleService; // ArticleService 클래스 테스트 위해 객체 선언
}
✔️ @Test : 해당 메서드가 테스트를 위한 코드라고 선언
13.2.2 index() 테스트하기
- 예상 데이터 작성하기
- 실제 데이터 획득하기
- 예상 데이터와 실제 데이터 비교해 검증하기
@Test
void index() {
// 1. 예상 데이터
Article a = new Article(1L, "가가가가", "1111"); // id Long타입이라 L 붙임
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());
}
- Array.asList()
- 입력된 배열이나 2개 이상의 동일 타입 데이터를 정적 리스트로 만들어 반환
- assertEquals(x, y)
- x 예상 데이터, y 실제 데이터 비교해 일치하면 테스트 통과 (JUnit에서 제공)
✔️ 예상 데이터와 실제 데이터가 일치하지 않은 경우
로그를 확인해보면 예상 데이터와 실제 데이터가 다른것을 확인할 수 있다.
13.2.3 show() 테스트하기
- 게시글 조회에 성공하는 경우와 실패하는 경우로 나눠서 테스트 진행
- 조회에 성공하는 다양한 경우 중 존재하는 id 입력하는 경우
- 조회에 실패하는 다양한 경우 중 존재하지 않는 id를 입력하는 경우
@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);
}
12.3.4 create() 테스트하기
- create 성공하는 경우와 실패하는 경우로 나눠서 테스트 케이스 작성
- 게시글 생성에 성공하는 다양한 상황 중 title과 content만 있는 dto 입력한 경우 테스트
- 게시글 생성에 실패하는 다양한 상황 중 id가 포함된 dto가 입력된 경우
@Test
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);
}
13.2.5 여러 케이스 한 번에 실행하기
롤백하지 않아서 문제 발생
❓ INSERT문을 실행해 새 데이터를 추가한 것을 확인할 수 있다.
❗️ 트랜잭션으로 테스트가 끝나면 롤백하도록 해야함
데이터 조회 제외 create, update, delete 테스트 진행 시 꼭 트랜잭션으로 묶어 테스트 종료 후 롤백 처리해 줘야 한다.
@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
@Transactional
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);
}
📌 update(), delete() 테스트 작성
테스트 함수들의 리턴값을 잘 확인하는 것이 중요!
package com.example.firstproject.service;
import com.example.firstproject.dto.ArticleForm;
import com.example.firstproject.entity.Article;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
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"); // id Long타입이라 L 붙임
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());
}
@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
@Transactional
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_입력() {
// 1. 예상 데이터
Long id = 1L;
String title = "바꿧졍";
String content = "1111";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = new Article(id, title, content);
// 2. 실제 데이터
Article article = articleService.update(id, dto);
// 3. 비교 및 검증
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_성공_존재하는_id_와_title만_있는_dto_입력(){
// 1. 예상 데이터
Long id = 1L;
String title = "마망";
String content = null;
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = new Article(1L, "마망", "1111");
// 2. 실제 데이터
Article article = articleService.update(id, dto);
// 3. 비교 및 검증
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_실패_존재하지_않는_id의_dto_입력(){
// 1. 예상 데이터
Long id = 4L;
String title = "뭐야이거";
String content = null;
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = null;
// 2. 실제 데이터
Article article = articleService.update(id, dto);
// 3. 비교 및 검증
assertEquals(expected, article);
}
@Test
@Transactional
void delete_성공_존재하는_id_입력() {
// 1. 예상 데이터
Long id = 1L;
Article expected = new Article(id, "가가가가", "1111");
// 2. 실제 데이터
Article article = articleService.delete(id);
// 3. 비교 및 검증
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void delete_실패_존재하지_않는_id_입력() {
// 1. 예상 데이터
Long id = -1L;
Article expected = null;
// 2. 실제 데이터
Article article = articleService.delete(id);
// 3. 비교 및 검증
assertEquals(expected, article);
}
}
'CS > 스프링부트3 자바 백엔드 개발 입문' 카테고리의 다른 글
[댓글 CRUD 만들기] 14장 | 댓글 엔티티와 리포지토리 만들기 (1) | 2025.03.08 |
---|---|
[REST API와 테스트 코드 작성하기] 12장 | 서비스 계층과 트랜잭션 (0) | 2025.03.05 |
[REST API와 테스트 코드 작성하기] 11장 | HTTP와 REST 컨트롤러 (0) | 2025.03.01 |
[REST API와 테스트 코드 작성하기] 10장 | REST API와 JSON (0) | 2025.02.27 |
[게시판 CRUD 만들기] 9장 | CRUD와 SQL 쿼리 종합 (0) | 2025.02.27 |