👉 Mission. 기존 Article 서비스를 검증하는 테스트 코드를 작성하시오
: 프로그램의 품질 검증을 위한 것, 의도대로 프로그램이 동작하는지 검증하는 것
< Test 3단계 >
1. 예상 시나리오 작성
2. 이를 실제 코드 결과와 비교하여 검증
3. 테스트 성공/실패 시,
3-1. (테스트 성공 시) 더 좋은 코드로 Refactoring
3-2. (테스트 실패 시) Debugging 을 통해 코드 수정
Test case : 소프트웨어 또는 시스템의 기능, 성능, 안정성 등을 검증하기 위해 설계된 테스트의 단위
TDD(Test Driven Development) : 1) 테스트 코드를 먼저 만들고, 2) 이를 통과하는 최소한의 코드로 시작해서, 3) 점진적으로 개선 및 확장해나가는 개발 방식
테스트 코드 만드는 방법
1. 테스트 하려는 메서드에 마우스 올리고
2. 우클릭 > Generate > Test > 해당 메서드 클릭 후 OK
3. 테스트 코드(양식) 자동 생성 (테스트 코드 위치 : src/test/java 내)
@SpringBootTest
: 해당 클래스가 스프링부트와 연동되어 테스팅된다는 의미의 어노테이션IntellliJ - 테스트 Run 할 때 뜨는 오류
: Test 코드 수행 시, Settings > Build > Build Tools > Gradle 가서 Gradle을 IntelliJ IDEA로 바꾸기
단, 다시 실제 서버 코드 수행 시, Gradle로 다시 바꿔줘야 정상 수행됨
-> 이전에는 그랬는데 이제는 갑자기 안바꿔도(Gradle로 해도) 잘 된다.. 뭐지?
index() 메서드 - 게시글 전체 조회
Test@Test
void index() {
// 예상 시나리오
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));
// 실제 결과
List<Article> articles = articleService.index();
// 비교
assertEquals(expected.toString(), articles.toString());
}
테스트 성공 시, 위와 같이 체크 표시가 뜬다.
즉, 예상 시나리오로 만든 expected
Article 리스트와 실제 articleService 에 구현한 index() 메서드를 실행했을 때 반환되는 articles
Article 리스트가 동일하다는 의미!
테스트 실패 시, 위와 같이 x 표시가 뜨며, 예상값과 실제값을 보여주어 무엇이 틀렸는지 알 수 있다!
show() 메서드 - 게시글 단건 조회
Testshow() 는 실패하는 경우의 테스팅도 진행해보자.
우선 성공부터,
@Test
void show_성공___존재하는_id_입력() {
// 예상 시나리오
Long id = 1L;
Article expected = new Article(id, "가가가가", "1111");
// 실제 결과
Article article = articleService.show(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
아래는 존재하지 않는 id를 입력해서 단건 조회에 실패하는 경우의 테스트이다.
@Test
void show_실패___존재하지_않는_id_입력() {
// 예상 시나리오
Long id = -1L;
Article expected = null;
// 실제 결과
Article article = articleService.show(id);
// 비교
assertEquals(expected, article);
}
null 값은 toString()으로 안찍히니까 제외한다.
create() 메서드 - 게시글 생성
Testcreate 역시 실패하는 경우의 테스팅도 진행해보자.
이것도 우선 성공 부터 ^^
@Test
void create_성공___title과_content만_있는_dto_입력() {
// 예상 시나리오
String title = "라라라라";
String content = "4444";
ArticleForm dto = new ArticleForm(null, title, content);
Article expected = new Article(4L, title, content);
// 실제 결과
Article article = articleService.create(dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
아래는 id를 포함해서 생성할 데이터가 넘어온 경우의 테스팅이다.
@Test
void create_실패___id가_포함된_dto_입력() {
// 예상 시나리오
String title = "라라라라";
String content = "4444";
ArticleForm dto = new ArticleForm(4L, title, content);
Article expected = null;
// 실제 결과
Article article = articleService.create(dto);
// 비교
assertEquals(expected, article);
}
위 테스트 코드를 수행할 때는 테스트 메서드 하나씩만 수행했는데, 한 번에 모든 테스트 메서드를 수행해볼 수도 있다.
그러나 이때, 문제가 발생한다. 분명 각자할 때는 잘만 성공하던 테스팅이 한 번에 할 경우 실패하는 것이다.
그 이유는 '한 번에 테스트를 해서' 이다.
만일 create가 index보다 먼저 수행될 경우, 4번 게시글이 추가가 되어 index() 테스트 코드를 수행할 때 실패가 뜨게 되는 것이다.
(JUnit5는 메서드명을 기준으로 실행 순서를 지정한다는데 나 같은 경우는 사실 실패가 안떴다. 그치만 실행순서를 임의 지정하지 않으면 오류가 뜰 수도 있으니까 일단 지금까지의 방식은 완벽하지 않음!)
그럼 테스트는 다 따로 해야 하냐고? 아니다.
이때 사용하는 게 바로 '트랜잭션'이다! 즉, 테스트가 끝나면 그 테스트를 롤백시켜주면 문제가 해결된다.
(게시글 조회 빼고 생성, 수정, 삭제 되는 테스트 코드는 전부 @Transactional
처리를 해주자)
여기서부터는 영상의 과제로 나온 update, delete 메서드 테스트를 수행한 것을 작성하겠다.
update() 메서드 - 게시글 수정
Test@Test
@Transactional
void update_성공___존재하는_id와_title과_content가_있는_dto_입력() {
// 예상 시나리오
Long id = 1L;
String title = "새로운거";
String content = "0000";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = new Article(id, title, content);
// 실제 결과
Article article = articleService.update(dto, id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_성공___존재하는_id와_title만_있는_dto_입력() {
// 예상 시나리오
Long id = 1L;
String title = "새로운거";
ArticleForm dto = new ArticleForm(id, title, null);
Article expected = new Article(id, title, "1111");
// 실제 결과
Article article = articleService.update(dto, id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void update_실패___존재하지_않는_id의_dto_입력() {
// 예상 시나리오
Long id = -1L;
String title = "새로운거";
String content = "0000";
ArticleForm dto = new ArticleForm(id, title, content);
Article expected = null;
// 실제 결과
Article article = articleService.update(dto, id);
// 비교
assertEquals(expected, article);
}
@Test
@Transactional
void update_실패___id만_있는_dto_입력() {
// 예상 시나리오
Long id = 1L;
ArticleForm dto = new ArticleForm(id, null, null);
Article expected = null;
// 실제 결과
Article article = articleService.update(dto, id);
// 비교
assertEquals(expected, article);
}
참고로 마지막 테스트 케이스인 "id만 있는 dto 입력"의 경우는 현재까지 구현한 Service로는 성공으로 수행된다.
하지만 사실 id만 넘겨주고 수정사항이 없으면 수정하는 의미가 없어지니 반드시 title 또는 content 중 하나는 수정해야 성공하게끔 Service 도 수정해주겠다!
if(target == null || id != article.getId() || (article.getTitle() == null && article.getContent() == null)){
log.info("잘못된 요청! id: {}, article: {}", id, article.toString());
return null;
}
(article.getTitle() == null && article.getContent() == null)
를 Service에 추가해주었다.
delete() 메서드 - 게시글 삭제
Test@Test
@Transactional
void delete_성공___존재하는_id_입력() {
// 예상 시나리오
Long id = 1L;
Article expected = new Article(1L, "가가가가", "1111");
// 실제 결과
Article article = articleService.delete(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void delete_실패___존재하지_않는_id_입력() {
// 예상 시나리오
Long id = -1L;
Article expected = null;
// 실제 결과
Article article = articleService.delete(id);
// 비교
assertEquals(expected, article);
}
Test란, 프로그램의 품질 검증을 위한 것, 의도대로 프로그램이 동작하는지 검증하는 것이다.
Test 는 크게 세 가지 단계로 진행된다. 1) 예상 시나리오를 작성하고, 2) 이를 실제 코드 결과와 비교하여 검증한 후, 3) 테스트 성공/실패를 통해 코드를 리팩토링 하거나 수정한다.
테스팅은 왜 중요할까?
소프트웨어 테스팅은 개발 과정에서 발생할 수 있는 오류를 사전에 발견하고 수정하여 최종 제품의 품질을 보장하는 데 핵심적인 과정이다. 이는 사용자의 만족도를 높이고, 시스템의 안정성과 신뢰성을 확보하는 데 중요하다. 왜냐하면 테스팅으로 개발 초기 단계에서 문제를 발견하고 수정함으로써, 개발 비용과 시간을 절약할 수 있기 때문이다.
또한 테스팅을 진행할 때, 통합 테스팅을 진행할 경우 반드시 테스트를 위한 트랜잭션을 주의하자!
강의 출처 : https://www.youtube.com/watch?v=_vDACE13Ubc&list=PLyebPLlVYXCiYdYaWRKgCqvnCFrLEANXt&index=1 [스프링 부트 입문 - 홍팍]