dlng23 2025. 1. 11. 18:03

13.1 테스트란

테스트: 프로그램의 품질을 검증하는 것, 의도대로 프로그램이 잘 동작하는지 확인하는 과정

 

테스트 코드 작성

  1. 예상 데이터 작성하기
  2. 실제 데이터 획득하기
  3. 예상 데이터와 실제 데이터 비교해 검증하기

작성한 코드가 테스트를 통과하면 지속적인 리팩터링, 실패하면 디버깅

 

테스트 코드는 다양한 경우를 대비해 작성 (테스트 케이스)

테스트 케이스는 성공할 경우 뿐 아니라 실패할 경우도 고려하여 세부적으로 작성

 

테스트 주도 개발(TDD, Test Driven Development)

일단 테스트 코드를 만든 후 이를 통과하는 최소한의 코드부터 시작해 점진적으로 코드를 개선 및 확장해 나가는 개발 방식

 

 

13.2 테스트 코드 작성하기

테스트 코드 기본 틀 만들기

service > ArticleService 열기

테스트하고 싶은 메서드에서 우클릭 후 Generate → Test 선택

Create Test 창에서 JUnit5 선택 후, Member에서 체크 후 [OK] 버튼 클릭

 

example.firstproject > service > ArticleServiceTest 로 이름과 경로가 자바 코드와 동일하게 생성됨

package com.example.firstproject.service;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class ArticleServiceTest {

    @Test
    void index() {
    }
}

 

테스트 코드를 스프링 부트와 연동하기 위해 설정

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
class ArticleServiceTest {
    @Autowired
    ArticleService articleService;
    @Test
    void index() {
    }
}

ArticleServiceTest 클래스 위에 @SpringBootTest 붙임

ArticleService 클래스를 테스트하기 위해 articleService 객체 선언, @Autowired 붙임

 

index() 테스트하기

package com.example.firstproject.service;

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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class ArticleServiceTest {
    @Autowired
    ArticleService articleService;
    @Test
    void index() {
        // 1. 예상 데이터
        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));

        // 2. 실제 데이터
        List<Article> articles = articleService.index();

        // 3. 비교 및 검증
        assertEquals(expected.toString(), articles.toString());
    }
}

예상 데이터와 실제 데이터가 일치하는지 비교하여 검증

Article c = new Article(4L, "다다다다", "3333"); 으로 3L → 4L로 수정하면 테스트에 통과하지 못했다고 나옴

 

show() 테스트하기

존재하는 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());
}

예상 데이터는 사용자가 ID가 1인 게시물의 조회를 요청했다고 가정하여 작성

실행하면 테스트에 잘 통과

예상 데이터를 일부러 틀려 title을 다르게 수정할 시 테스트에 통과하지 못함 

 

존재하지 않는 id를 입력해 실패하는 경우 테스트

@Test
void show_실패_존재하지_않는_id_입력() {
    // 1. 예상 데이터
    Long id = -1L;
    Article expected = null;
    // 2. 실제 데이터
    Article article = articleService.show(id);
    // 3. 비교 및 검증
    assertEquals(expected, article);
}

예상 데이터를 존재하지 않는 id인 -1을 조회한다고 가정하여 작성

DB에 조회되는 내용이 없어 null을 반환할 것이므로 expected 객체에 null 저장

테스트 돌려보면 실제로 null을 반환하기때문에 테스트에 잘 통과

 

create() 테스트하기

title과 content만 있는 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());
}

사용자가 새 게시물을 생성한 상황을 가정하여 예상 데이터 작성

id는 DB가 자동으로 생성하므로 title과 content 필드만 선언하고 dto 객체로 생성

예상 데이터의 id는 자동으로 생성될 값인 4L를 써 줌

실제 데이터와 비교, 테스트를 돌려 보면 정상적으로 통과

 

id가 포함된 dto가 입력된 경우 테스트 (실패 상황)

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

예상 데이터는 사용자가 새 게시물을 생성할 때 id를 써 줄 필요가 없는데도 id를 입력한 상황

게시글 생성할 때 id를 넣으면 오류가 나고 null이 반환되므로 expected 객체에 null 저장

비교할 때 null은 toString() 메서드를 호출할 수 없으므로 expected, article로 씀

테스트를 돌려 보면 정상적으로 통과

 

여러 테스트 케이스 한 번에 실행하기

각 테스트 케이스를 돌리지 않고 ArticleServiceTest 클래스 자체를 실행하면 여러 테스트 케이스를 한 번에 돌릴 수 있음

Run 'ArticleServiceTest' 선택

→ index() 통과하지 못함

 

예상 데이터 값에 3개의 데이터가 있는데, 실제 데이터 값이 4개가 있음

→ 롤백하지 않아 생긴 문제

index() 메서드 실행 전에 다른 테스트 케이스 메서드를 실행하면서 데이터를 하나 추가하였기때문에 테스트가 끝나면 롤 백하도록 해야 함

 

데이터를 조회하는 테스트를 제외하고 데이터를 생성, 수정, 삭제하는 테스트를 할 때는 롤백 처리를 해주어야 함

메서드 위에 @Transactional 을 추가하여 테스트가 끝난 후 변경된 데이터를 처음으로 되돌리도록 함

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
class ArticleServiceTest {
    @Autowired
    ArticleService articleService;
    @Test
    void index() {
        // 1. 예상 데이터
        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));
        // 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);
    }

    @Transactional
    @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());
    }

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

 

더보기

테스트 코드 실행 오류

IntelliJ Settings에서 Gradle 검색 후 Run tests using: Gradle (Default) → IntelliJ IDEA로 바꾸니 테스트 코드 정상적으로 실행됨


왜 그런가에 대한 ChatGPT 의 답변 >

IntelliJ에서 Run tests using 설정을 Gradle에서 IntelliJ로 변경했을 때 테스트 실행이 정상적으로 된 이유는 다음과 같은 환경 차이 때문입니다.


1. Gradle과 IntelliJ의 테스트 실행 환경 차이

Gradle

  • Gradle을 사용해 테스트를 실행하면 Gradle이 별도의 JVM과 빌드 환경을 설정하여 테스트를 실행합니다.
  • Gradle의 의존성 관리와 빌드 설정이 정확하지 않으면 테스트가 실패하거나 실행되지 않을 수 있습니다.
    • 예: Gradle 버전이 오래되었거나, build.gradle 파일에 테스트와 관련된 설정이 누락된 경우.
  • Gradle이 관리하는 JVM과 IntelliJ가 사용하는 JVM 버전이 다를 수도 있습니다.

IntelliJ

  • IntelliJ로 테스트를 실행하면 IDE가 제공하는 기본 실행 환경과 JVM을 사용합니다.
  • IntelliJ는 프로젝트의 classpath를 직접 참조하므로, Gradle 설정의 영향을 받지 않고 실행됩니다.
  • 빠른 피드백 루프를 제공하기 위해 IntelliJ는 내부적으로 간소화된 방식으로 테스트를 실행합니다.

2. 문제의 원인

Gradle로 테스트 실행 시 문제가 발생했던 이유는 여러 가지가 있을 수 있습니다:

(1) Gradle 테스트 설정 문제

  • build.gradle 파일에 테스트와 관련된 설정이 누락되었거나, Gradle이 필요한 플러그인/의존성을 제대로 로드하지 못한 경우.
    • 예: testImplementation으로 테스트 라이브러리를 추가하지 않았거나, tasks.test 설정이 잘못된 경우.

(2) Gradle의 JVM 버전 문제

  • Gradle이 사용하는 JVM 버전이 프로젝트에서 요구하는 버전과 맞지 않을 경우, 테스트 실행이 실패할 수 있습니다.
    • 예: 프로젝트가 Java 17을 요구하지만 Gradle이 Java 11로 실행 중인 경우.

(3) 캐시 문제

  • Gradle의 빌드 캐시가 손상되었거나 오래된 경우, 의존성이 제대로 로드되지 않아 테스트가 실패할 수 있습니다.
    • ./gradlew clean 명령어로 캐시를 초기화하면 해결될 수 있음.

(4) IntelliJ와 Gradle 간 classpath 불일치

  • IntelliJ와 Gradle 간에 classpath 설정이 다르게 되어 있을 경우, Gradle로 실행하면 필요한 의존성을 찾지 못하는 반면, IntelliJ에서는 잘 동작할 수 있습니다.

3. 왜 IntelliJ로 실행하면 잘 동작했는가?

  • IntelliJ는 프로젝트의 설정(.iml 파일 및 IDE 설정)을 기반으로 테스트를 실행하므로, Gradle 설정에 문제가 있더라도 영향을 덜 받습니다.
  • IntelliJ의 내부 테스트 실행 환경은 더 직관적이고, 특별한 추가 설정 없이도 대부분의 테스트를 실행할 수 있습니다.

4. 해결 방법

Gradle에서도 테스트를 올바르게 실행할 수 있도록 다음 단계를 확인하세요:

(1) Gradle 테스트 설정 확인

  • build.gradle 파일에 필요한 테스트 의존성이 모두 추가되었는지 확인하세요.
    dependencies {
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    

(2) Gradle JVM 확인

  • Gradle이 사용하는 JVM 버전을 확인하고, 프로젝트에 맞는 버전인지 점검하세요.
    ./gradlew -version
    
    필요한 경우 IntelliJ에서 Gradle JVM 설정을 변경하세요:
    • Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JVM.

(3) Gradle 캐시 정리

  • Gradle의 빌드 캐시를 정리하여 문제가 되는 잔여 데이터를 제거하세요.
    ./gradlew clean
    

(4) Gradle과 IntelliJ 동기화

  • IntelliJ에서 Gradle 프로젝트를 동기화하여 모든 설정이 최신 상태로 유지되도록 합니다:
    • File > Sync Project with Gradle Files 클릭.

(5) Gradle 테스트 실행

  • Gradle에서 직접 테스트를 실행해 문제가 해결되었는지 확인합니다.
    ./gradlew test
    

요약

  • IntelliJ로 테스트 실행 시 IDE가 Gradle 설정을 우회하므로 더 간단하게 테스트를 실행할 수 있습니다.
  • Gradle 테스트가 실패했다면 설정 문제, JVM 버전 불일치, Gradle 캐시 등이 원인일 가능성이 높습니다.
  • Gradle 설정을 점검하고, IntelliJ와 Gradle 간 환경을 일치시키면 Gradle에서도 문제가 해결될 것입니다. 😊