2023.03.20 테스트 코드 작성하기
ArticleService의 동작검증을 위해서 Test코드를 작성 및 수행해보자.
프로그램의 품질 검증을 위한것으로
우리의 의도대로 프로그램이 동작하는지 확인하는 것.
TDD는 Test-Driven Development의 약어로, 소프트웨어 개발 방법론 중 하나입니다. 이 방법론은 소프트웨어를 개발할 때, 먼저 테스트를 작성하고, 그 다음에 코드를 작성하는 것을 강조합니다.
TDD를 따르는 개발자는 먼저 요구사항을 이해하고, 이를 바탕으로 테스트를 작성합니다. 이 테스트는 코드의 기능을 검증하고, 문제가 있는 부분을 찾는 데 도움이 됩니다. 그 다음에는 이 테스트를 통과시키기 위한 코드를 작성하고, 작성한 코드가 테스트를 통과하는지 확인합니다.
TDD를 따르는 개발자는 코드를 작성하기 전에 테스트를 먼저 작성하기 때문에, 코드의 품질과 안정성을 높일 수 있습니다. 또한, 코드의 문제를 발견하고 수정하는 데 소요되는 시간을 줄일 수 있습니다. 이러한 이유로, TDD는 소프트웨어 개발에서 매우 인기 있는 방법론 중 하나입니다.
테스트하고 싶은 메소드 오른쪽마우스 - Generate - Test
ArticleServiceTest 생성됨.
ArticleServiceTest에
@SpringBootTest추가.
@Autowired로 ArticleService 불러오기.
package com.example.firstproject.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 1.@SpringBootTest 추가. 해당 클래스는 스프링부트와 연동되어 테스팅된다!
class ArticleServiceTest {
// 2. ArticleService를 DI
@Autowired ArticleService articleService;
@Test
void index() {
}
}
@Test
void index() {
// 예상
// 모든 게시물을 불러오면 data.sql에 저장했던 데이터들이 불러와 질것이라는 예상.
Article a = new Article(1L, "가가가가", "11111");
Article b = new Article(2L,"22가가가가", "2211111");
Article c = new Article(3L, "33가가가가", "3311111");
// List로 만들기.
List<Article> expected = new ArrayList<Article>(Arrays.asList(a,b,c));
// 실제
List<Article> articles = articleService.index();
// 비교
// expected(예상)와 articles(실제) 비교
assertEquals(expected.toString(), articles.toString());
}
코드 작성 후 화살표 눌러서 실행.
일치한 결과.
❌ 일치하지 않을 때
일부터 id값을 다르게 해서 일치하지 않게 해보자.
로그를 확인해보면 Expected와 Actual의 id가 일치하지 않아 Test가 실패했다.
성공과 실패하는 경우를 나눠서 Test 해보기.
@Test
void show_성공____존재하는_id_입력() {
// 예상
Long id = 1L;
Article expected = new Article(id, "가가가가", "11111");
// 실제
Article article = articleService.show(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
void show_실패____존재하지_않는_id_입력() {
// 예상
Long id = -1L;
Article expected = null;
// ArticleService에서 show를 했을때 데이터가 없으면 null을 리턴하기로 했기때문에 예상값은 null이 되어야한다.
// ArticleService -> return articleRepository.findById(id).orElse(null);
// 실제
Article article = articleService.show(id);
// 비교
// null을 toString()을 호출할 수 없음.
assertEquals(expected, article);
}
@Test
void create_성공____title과_content만_있는_dto_입력() {
// 예상
// title,content를 만들고 이걸 이용해서 dto 생성.
String title = "라라";
String content = "44";
ArticleForm dto = new ArticleForm(null, title, content);
// 예상 결과 값은 기존에 1,2,3 데이터가 있었기 때문에 id가 4인 객체가 생성될 것이다.
Article expected = new Article(4L, title, content);
// 실제
Article article = articleService.create(dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
void create_실패____id가_포함된_dto_입력() {
// 예상
// title,content를 만들고 이걸 이용해서 dto 생성.
// dto를 만들때 id는 자동생성인데 id를 함께 전달했을때 null이 오도록 코드를 작성해놨다.
String title = "라라";
String content = "44";
ArticleForm dto = new ArticleForm(4L, title, content);
Article expected = null;
//if(article.getId() != null) {
// return null;
// }
// 실제
Article article = articleService.create(dto);
// 비교
assertEquals(expected, article);
}
클래스 선언부 옆에 화살표를 눌러서 모든 Test코드 Test하기.
❌ 그런데 하나하나 돌릴때는 다 성공했는데
index가 실패했다.
로그를 보면 예상은 id값 1,2,3만 예상햇는데
id가 4번인 Article이 생성됐다.
그 이유는 위에서 create Test를 진행해서 id가 4번인 Article이 추가 됐기때문이다.
그럼 어떻게 할까????
✨ 트랜잭션처리를 해서 Test를 완료하면 롤백하도록 해야한다. ✨
데이터 조회가 아닌
생성,변경,삭제가 될 경우에는 트랜잭션으로 묶어서 롤백되도록 해야한다.
데이터가 생성되는
"create성공____title과_content만있는dto입력" 메소드에 @Transactional추가.
@Test
@Transactional
void create_성공____title과_content만_있는_dto_입력() {
...
}
다시 Test진행해보면 모두 다 성공한 것을 확인할 수 있당 😊
package com.example.firstproject.service;
import com.example.firstproject.dto.ArticleForm;
import com.example.firstproject.entity.Article;
import org.junit.jupiter.api.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 // 1.@SpringBootTest 추가. 해당 클래스는 스프링부트와 연동되어 테스팅된다!
class ArticleServiceTest {
// 2. ArticleService를 DI
@Autowired ArticleService articleService;
@Test
void index() {
// 예상
// 모든 게시물을 불러오면 data.sql에 저장했던 데이터들이 불러와 질것이라는 예상.
Article a = new Article(1L, "가가가가", "11111");
Article b = new Article(2L,"22가가가가", "2211111");
Article c = new Article(3L, "33가가가가", "3311111");
// List로 만들기.
List<Article> expected = new ArrayList<Article>(Arrays.asList(a,b,c));
// 실제
List<Article> articles = articleService.index();
// 비교
// expected(예상)와 articles(실제) 비교
assertEquals(expected.toString(), articles.toString());
}
@Test
void show_성공____존재하는_id_입력() {
// 예상
Long id = 1L;
Article expected = new Article(id, "가가가가", "11111");
// 실제
Article article = articleService.show(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
void show_실패____존재하지_않는_id_입력() {
// 예상
Long id = -1L;
Article expected = null;
// ArticleService에서 show를 했을때 데이터가 없으면 null을 리턴하기로 했기때문에 예상값은 null이 되어야한다.
// ArticleService -> return articleRepository.findById(id).orElse(null);
// 실제
Article article = articleService.show(id);
// 비교
// null을 toString()을 호출할 수 없음.
assertEquals(expected, article);
}
@Test
@Transactional
void create_성공____title과_content만_있는_dto_입력() {
// 예상
// title,content를 만들고 이걸 이용해서 dto 생성.
String title = "라라";
String content = "44";
ArticleForm dto = new ArticleForm(null, title, content);
// 예상 결과 값은 기존에 1,2,3 데이터가 있었기 때문에 id가 4인 객체가 생성될 것이다.
Article expected = new Article(4L, title, content);
// 실제
Article article = articleService.create(dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
void create_실패____id가_포함된_dto_입력() {
// 예상
// title,content를 만들고 이걸 이용해서 dto 생성.
// dto를 만들때 id는 자동생성인데 id를 함께 전달했을때 null이 오도록 코드를 작성해놨다.
String title = "라라";
String content = "44";
ArticleForm dto = new ArticleForm(4L, title, content);
Article expected = null;
//if(article.getId() != null) {
// return null;
// }
// 실제
Article article = articleService.create(dto);
// 비교
assertEquals(expected, article);
}
@Test
@Transactional
void update_성공____존재하는_id와_title_content가_있는_dto_입력() {
// 예상
// title,content를 만들고 이걸 이용해서 dto 생성.
Long id = 1L;
String title = "라라";
String content = "44";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = new Article(1L, title, content);
// 실제
Article article = articleService.update(id,dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_성공____존재하는_id와_title만_있는_dto_입력() {
// 예상
// title,content를 만들고 이걸 이용해서 dto 생성.
Long id = 1L;
String title = "라라";
ArticleForm dto = new ArticleForm(id, title,"ddd");
Article expected = new Article(1L, title, "ddd");
// 실제
Article article = articleService.update(id,dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_실패____존재하지_않는_id의_dto_입력() {
// 예상
Long id = 10L;
ArticleForm dto = new ArticleForm(id, "ddd","ddd");
Article expected = null;
// 실제
Article article = articleService.update(id,dto);
// 비교
assertEquals(expected, article);
}
@Test
@Transactional
void update_실패____id만_있는_dto_입력() {
// 예상
Long id = 1L;
ArticleForm dto = new ArticleForm(id, "가가가가", "11111");
Article expected = new Article(id, "가가가가", "11111");
// 실제
Article article = articleService.update(id,dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void delete_성공____존재하는_id_입력() {
// 예상
Long id = 1L;
Article expected = new Article(id, "가가가가","11111");
// 실제
Article article = articleService.delete(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void delete_실패____존재하지_않는_id_입력() {
// 예상
Long id = 10L;
Article expected = null;
// 실제
Article article = articleService.delete(id);
// 비교
assertEquals(expected, article);
}
}