본문 바로가기

⭐ SpringBoot/𝄜 게시판 with SpringBoot

20. 테스트 TDD

728x90
반응형

#  요약

ArticleService에 있는 여러가지 CRUD 기능들을 테스트로 작성해 보았다.

이러한 테스트는 테스트라는 패키지 폴더안에 위치 하였고, 테스트는 @SpringBootTest와 @Test 어노테이션을 사용하여 진행을 하였고, 테스트의 진행 순서는 예상과 실제와 비교 3단계로 작성하여 진행 되었고, 이러한 테스트는 성공하는 경우의 테스트 뿐만 아니라 실패하는 경우의 테스트도 다양하게 존재 할 수 있다. 더 나아가 성공과 실패 또한 다양한 경우의 테스트 케이스를 생성하여 테스트가 진행 가능 하다.

# 기존 Article 서비스를 검증하는 테스트 코드를 작성 하시오.

 

테스트란?

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

테스트를 활용하면 프로그램을 검증하는 일련의 과정을 최소화 할 수 있다.

이러한 테스트는 크게 3단계로 나눌수 있다.

1. 예상시나리오를 작성

2. 이를 실제 코드 결과와 비교 및 검증

3. 테스트를 통과한경우 리팩토링을 진행, 실패한 경우에는 디버깅을 통해 코드를 고쳐주면 된다.

이렇게 작성하는 것을 테스트 케이스라고 한다.

테스트 케이스는 크게 성공과 실패로 나눌수 있고, 이 또한 조건에 따라 다양한 경우로 작성 될 수 있다.

이렇게 개발을 진행하는 것을 TDD(Test Driven Development) 라고 한다.

TDD 란?

테스트 코드를 먼저 만들고 이를 통과하는 최소한의 코드를 통해 점진적으로 개선 및 확장해 나가는 개발 방식 이다.

실제 코드를 통해 테스트 코드의 작성과 수행을 연습

테스트 코드의 틀 잡기

ArticleService 클래스에서 테스트할 메소드에 마우스 우측 버튼을 클릭 →  Go To →  Test →  Create New Test →  테스트 원하는 메소드를 선택 →  OK 버튼 클릭하여 테스트 메소드를 생성 한다. 해당 테스트 코드는 test 라는 폴더안에 생성이 된다.

클래스의 SpringBootTest 어노테이션 추가

@SpringBootTest // 해당 클래스는 스프링부트와 연동되어 테스팅 된다.
class ArticleServiceTest {

    @Autowired
    ArticleService articleService; // ArticleService를 테스트 해야 하므로 Autowired로 불러온다.

@Test 는 해당 메소드가 테스트를 위한 코드라고 선언하는 어노테이션 이다.

index 메소드를 테스트

예상 값과 실제 값 그리고 마지막은 예상값과 실제값을 비교

예상 되는 데이터는 기존에 생성한 data.sql 파일에 존재 한다.

data.sql 파일의 내용은 아래와 같다.

INSERT INTO article(id, title, content) VALUES (1, '가가가가가', '11111');
INSERT INTO article(id, title, content) VALUES (2, '나나나나나', '22222');
INSERT INTO article(id, title, content) VALUES (3, '다다다다다', '33333');

List<Article> expected = new ArrayList<Article>(Arrays.asList(a, b, c)); → new ArrayList에 <Article>을 담을건데, 이것을 편하게 Array.asList를 사용해서 (a, b, c)를 한번에 리스트로 만든다. 이것을 expected라는 이름으로 담고, 이것이 Article의 묶음 이므로 List<Article>로 선언해 준다.

이렇게 해서 예상 데이터 expected와 실제데이터 articles를 작성을 하였다.

해당 데이터의 비교는 assertEquals(expected.toString(), articles.toString()); → assertEquals를 사용하여 예상 데이터 expected의 내용과 실제 데이터 articles의 내용을 비교 한다.

@Test
void index() {
    //예상 data.sql 파일을 참조하여 데이터를 생성 하므로 아래와 같이 나올 것이다.
    Article a = new Article(1L, "가가가가가", "11111");
    Article b = new Article(2L, "나나나나나", "22222");
    Article c = new Article(3L, "다다다다다", "33333");
    List<Article> expected = new ArrayList<Article>(Arrays.asList(a, b, c));

    //실제
    List<Article> articles = articleService.index();

    //비교
    assertEquals(expected.toString(), articles.toString());
}

정상적으로 테스트가 끝나면 아래와 같이 테스트 결과가 표시 된다.

②번을 보면 정상적으로 체크가 완료된 것을 볼 수 있다. 이 표시는 무엇을 의미 하냐면, 예상한 값과 실제 데이터가 일체 했다는 내용으로 이해하면 된다.

잘못된 데이터를 기반으로 테스팅 수행 시 어떤 결과값이 나오는지 테스트

왼쪽 결과창에 테스트가 실패하여 노란색 아이콘으로 변경 되었고, 오른쪽 칸에서는 실패한 값의 예상값과 실제값이 표시되는것을 볼 수 있다.

콘솔 창에서 에러 발생 시 표출되는 예상값과 실제 값, 해당 값이 틀려서 에러를 발생 시킴

기대한 값은 아래와 같지만 실제 값은 기대한 값돠 달라서 에러가 발생 하였다.

Expected :[Article(id=1, title=가가가가가, content=11111), Article(id=2, title=나나나나나, content=22222), Article(id=4, title=다다다다다, content=33333)]
Actual   :[Article(id=1, title=가가가가가, content=11111), Article(id=2, title=나나나나나, content=22222), Article(id=3, title=다다다다다, content=33333)]

ArticleService의 show 메소드를 테스트

테스트 코드 작성 시 이미 있는 파일이라는 오류 메세지가 출력 된다. OK를 눌러서 생성을 하면, 기존의 있던 파일에 해당 테스트 내용이 추가된다.

이번 테스트는 2단계로 나누어 진행 한다. 첫번째 단계는 show 메소드가 성공하는 단계이고, 다음 단계는 show 메소드가 실패하는 단계로 구분지어 생성한다.

테스트 성공하는 조건도 여러개가 존재 하나, 여기서는 존재하는 id를 입력하여 성공 케이스를 만든다.

실제 값 : Article article = articleService.show(id); → Long id = 1L; 이기 때문에 1번째 라인의 값을 실제 값으로 받아 올 것이다. 

사용자가 아이디 값을 입력 : Long id = 1L;

예상하는 반환값 : Article expected = new Article(id, "가가가가가", "11111");

비교 값 : assertEquals(expected.toString(), article.toString()); → expected.toString() 내용과 article.toString()내용을 비교하여 비교한 값이 맞으면 테스트가 성공했다고 표시가 된다.

@Test
void show_성공___존재하는_id_입력() {
    // 예상
    Long id = 1L;
    Article expected = new Article(id, "가가가가가", "11111");

    // 실제
    Article article = articleService.show(id);

    // 비교
    assertEquals(expected.toString(), article.toString());
}

실패하는 케이스는 존재하지 않는 id를 입력했을때 결과를 출력

-1L 아이디는 존재하지 않으니 예상하는 리턴값은 null 값이 나올 것이다.

실제 articleService에서 .orElse(null);로 값을 받기 때문에 존재 하지 않는 id가 입력 될 경우 null값이 반환될 것이다.

null은 to.String() 메소드로 출력 할 수 없으므로, 해당 값만을 넣어준다. → assertEquals(expected, article);

따라서 예상값과 실제값이 둘다 null 값임을 검증 하였다.

@Test
void show_실패___존재하지_않는_id_입력() {
    // 예상
    Long id = -1L;
    Article expected = null;

    // 실제
    Article article = articleService.show(id);

    // 비교
    assertEquals(expected, article);
}

ArticleService의 create 메소드를 테스트

해당 title값과 content값을 기반으로 예상되는 입력값 dto를 생성 한다.

예상되는 dto값을 실제 코드에 넣어서 반환되는 예상되는 값은 new Article(4L, title, content);와 같은 것인데, 4번째 라인에 title과 content값을 넣는다고 보면 된다.

그렇다면 예상값과 실제값이 세팅이 완료 되었다. 비교를 수행한다.

assertEquals(expected.toString(), article.toString()); → 내용으로 비교를 수행한다.

    @Test
    @Transactional
    void create_성공___title과_content만_있는_dto_입력() {
        // 예상
        String title = "라라라라라";
        String content = "44444";
        ArticleForm dto = new ArticleForm(null, title, content);
        Article expected = new Article(4L, title, content);

        // 실제
        Article article = articleService.create(dto);

        // 비교
        assertEquals(expected.toString(), article.toString());
    }

실패하는 케이스를 생성, ArticleService 코드에서 id가 존재하는 경우에 null을 반환하도록 실제 데이터가 구현 되어 있으므로 id를 입력하는 곳에 값이 null이 되어야 할것이다. ArticleForm dto = new ArticleForm(4L, title, content); → id값에 null이 아니라 존재하는 id를 입력하였으므로 실패하는 테스트가 성공적으로 진행된것을 볼 수 있다.

@Test
@Transactional
void create_실패___id가_포함된_dto_입력() {
    // 예상
    String title = "라라라라라";
    String content = "44444";
    ArticleForm dto = new ArticleForm(4L, title, content);
    Article expected = null;

    // 실제
    Article article = articleService.create(dto);

    // 비교
    assertEquals(expected, article);
}

테스트 케이스를 한번에 수행하는 방법은 해당 클래스의 상단 부분에 아이콘을 클릭하여 수행하면 해당 클래스의 테스트 케이스를 전체적으로 수행 할 수 있다.

테스트 시 데이터가 조회가 아닌 생성 및 변경 시 해당 메소드 실행을 롤백해 줘야 다른 테스트 케이스와 동시에 실행 했을때 문제를 최소화 할 수 있다. 예를들어 A라는 테스트 메소드가 데이터를 생성 후 바로 B라는 테스트 케이스가 실행 될때 예상했던 데이터 값과 실제 값이 불일치 하여 오류가 발생하게 된다.

이런 문제를 예방하기 위해서 데이터가 생성 및 변경시에 해당 메소드를 롤백 할 수 있는 @Transactional 어노테이션을 추가해 줘야 한다.

지금까지 했던 TDD 작업 중에서는 create 작업이 데이터를 생성하는 메소드 이므로 해당 메소드에 @Transactional이라는 어노테이션을 붙여 줘야 한다.

 

추가적인 테스트 케이스 생성 과제

@Test
@Transactional
void update_성공____존재하는_id와_title_content가_있는_dto_입력() {
}

@Test
@Transactional
void update_실패____존재하지_않는_id의_dto_입력() {
}

@Test
@Transactional
void update_실패____id만_있는_dto_입력() {
}

@Test
@Transactional
void delete_성공____존재하는_id_입력() {
}

@Test
@Transactional
void delete_실패____존재하지_않는_id_입력() {
}
728x90
반응형