SpringBoot(홍팍) - 테스트 코드 작성하기

정원·2023년 3월 20일
0

SpringBoot

목록 보기
25/34

2023.03.20 테스트 코드 작성하기

ArticleService의 동작검증을 위해서 Test코드를 작성 및 수행해보자.

Test 란?

프로그램의 품질 검증을 위한것으로
우리의 의도대로 프로그램이 동작하는지 확인하는 것.

TDD(Test Driven Development)란?

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() {

    }
}

index() Test

@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가 실패했다.

show 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);
    }

create Test

성공

@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하기

클래스 선언부 옆에 화살표를 눌러서 모든 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진행해보면 모두 다 성공한 것을 확인할 수 있당 😊

ArticleService 전체코드


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);
    }
}

0개의 댓글