Cloud Native Spring in Action

JUnit5를 이용한 단위 테스트

기록해연 2025. 2. 7. 15:59

소스 작성중 JUnit 이 임포트가 안되는 문제가 생김.

그래들엔 분명 spring-boot-starter-test 의존성이 있고, 책에선 이게 JUnit5, 모키토, 어서트J 같은 테스트 라이브러리를 프로젝트로 임포트 한다고 써있는데...!

 

package com.polarbookshop.catalogservice.domain;

import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeAll;

//Book 객체의 유효성 검사 제약조건을 검증하기 위한 단위 테스트
public class BookValidationTests {
    private static Validator validator;

    @BeforeAll
    static void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }
}

 

JUnit4에서 쓰이는

import org.junit.BeforeClass;

도 사용해봤는데 전혀 들어먹지를 않음.

 

더보기
C:\Y\Cloud Native Spring in Action\catalog-service>gradlew.bat dependencies --configuration testRuntimeClasspath

> Task :dependencies

------------------------------------------------------------
Root project 'catalog-service'
------------------------------------------------------------

testRuntimeClasspath - Runtime classpath of source set 'test'.
+--- org.springframework.boot:spring-boot-starter-web -> 3.4.1
|    +--- org.springframework.boot:spring-boot-starter:3.4.1
|    |    +--- org.springframework.boot:spring-boot:3.4.1
|    |    |    +--- org.springframework:spring-core:6.2.1
|    |    |    |    \--- org.springframework:spring-jcl:6.2.1
|    |    |    \--- org.springframework:spring-context:6.2.1
|    |    |         +--- org.springframework:spring-aop:6.2.1
|    |    |         |    +--- org.springframework:spring-beans:6.2.1
|    |    |         |    |    \--- org.springframework:spring-core:6.2.1 (*)
|    |    |         |    \--- org.springframework:spring-core:6.2.1 (*)
|    |    |         +--- org.springframework:spring-beans:6.2.1 (*)
|    |    |         +--- org.springframework:spring-core:6.2.1 (*)
|    |    |         +--- org.springframework:spring-expression:6.2.1
|    |    |         |    \--- org.springframework:spring-core:6.2.1 (*)
|    |    |         \--- io.micrometer:micrometer-observation:1.14.2
|    |    |              \--- io.micrometer:micrometer-commons:1.14.2
|    |    +--- org.springframework.boot:spring-boot-autoconfigure:3.4.1
|    |    |    \--- org.springframework.boot:spring-boot:3.4.1 (*)
|    |    +--- org.springframework.boot:spring-boot-starter-logging:3.4.1
|    |    |    +--- ch.qos.logback:logback-classic:1.5.12
|    |    |    |    +--- ch.qos.logback:logback-core:1.5.12
|    |    |    |    \--- org.slf4j:slf4j-api:2.0.15 -> 2.0.16
|    |    |    +--- org.apache.logging.log4j:log4j-to-slf4j:2.24.3
|    |    |    |    +--- org.apache.logging.log4j:log4j-api:2.24.3
|    |    |    |    \--- org.slf4j:slf4j-api:2.0.16
|    |    |    \--- org.slf4j:jul-to-slf4j:2.0.16
|    |    |         \--- org.slf4j:slf4j-api:2.0.16
|    |    +--- jakarta.annotation:jakarta.annotation-api:2.1.1
|    |    +--- org.springframework:spring-core:6.2.1 (*)
|    |    \--- org.yaml:snakeyaml:2.3
|    +--- org.springframework.boot:spring-boot-starter-json:3.4.1
|    |    +--- org.springframework.boot:spring-boot-starter:3.4.1 (*)
|    |    +--- org.springframework:spring-web:6.2.1
|    |    |    +--- org.springframework:spring-beans:6.2.1 (*)
|    |    |    +--- org.springframework:spring-core:6.2.1 (*)
|    |    |    \--- io.micrometer:micrometer-observation:1.14.2 (*)
|    |    +--- com.fasterxml.jackson.core:jackson-databind:2.18.2
|    |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.18.2
|    |    |    |    \--- com.fasterxml.jackson:jackson-bom:2.18.2
|    |    |    |         +--- com.fasterxml.jackson.core:jackson-annotations:2.18.2 (c)
|    |    |    |         +--- com.fasterxml.jackson.core:jackson-core:2.18.2 (c)
|    |    |    |         +--- com.fasterxml.jackson.core:jackson-databind:2.18.2 (c)
|    |    |    |         +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2 (c)
|    |    |    |         +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2 (c)
|    |    |    |         \--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.18.2 (c)
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.18.2
|    |    |    |    \--- com.fasterxml.jackson:jackson-bom:2.18.2 (*)
|    |    |    \--- com.fasterxml.jackson:jackson-bom:2.18.2 (*)
|    |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.18.2 (*)
|    |    |    +--- com.fasterxml.jackson.core:jackson-databind:2.18.2 (*)
|    |    |    \--- com.fasterxml.jackson:jackson-bom:2.18.2 (*)
|    |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2
|    |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.18.2 (*)
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.18.2 (*)
|    |    |    +--- com.fasterxml.jackson.core:jackson-databind:2.18.2 (*)
|    |    |    \--- com.fasterxml.jackson:jackson-bom:2.18.2 (*)
|    |    \--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.18.2
|    |         +--- com.fasterxml.jackson.core:jackson-core:2.18.2 (*)
|    |         +--- com.fasterxml.jackson.core:jackson-databind:2.18.2 (*)
|    |         \--- com.fasterxml.jackson:jackson-bom:2.18.2 (*)
|    +--- org.springframework.boot:spring-boot-starter-tomcat:3.4.1
|    |    +--- jakarta.annotation:jakarta.annotation-api:2.1.1
|    |    +--- org.apache.tomcat.embed:tomcat-embed-core:10.1.34
|    |    +--- org.apache.tomcat.embed:tomcat-embed-el:10.1.34
|    |    \--- org.apache.tomcat.embed:tomcat-embed-websocket:10.1.34
|    |         \--- org.apache.tomcat.embed:tomcat-embed-core:10.1.34
|    +--- org.springframework:spring-web:6.2.1 (*)
|    \--- org.springframework:spring-webmvc:6.2.1
|         +--- org.springframework:spring-aop:6.2.1 (*)
|         +--- org.springframework:spring-beans:6.2.1 (*)
|         +--- org.springframework:spring-context:6.2.1 (*)
|         +--- org.springframework:spring-core:6.2.1 (*)
|         +--- org.springframework:spring-expression:6.2.1 (*)
|         \--- org.springframework:spring-web:6.2.1 (*)
+--- org.springframework.boot:spring-boot-starter-validation -> 3.4.1
|    +--- org.springframework.boot:spring-boot-starter:3.4.1 (*)
|    +--- org.apache.tomcat.embed:tomcat-embed-el:10.1.34
|    \--- org.hibernate.validator:hibernate-validator:8.0.2.Final
|         +--- jakarta.validation:jakarta.validation-api:3.0.2
|         +--- org.jboss.logging:jboss-logging:3.4.3.Final -> 3.6.1.Final
|         \--- com.fasterxml:classmate:1.5.1 -> 1.7.0
+--- org.springframework.boot:spring-boot-starter-test -> 3.4.1
|    +--- org.springframework.boot:spring-boot-starter:3.4.1 (*)
|    +--- org.springframework.boot:spring-boot-test:3.4.1
|    |    +--- org.springframework.boot:spring-boot:3.4.1 (*)
|    |    \--- org.springframework:spring-test:6.2.1
|    |         \--- org.springframework:spring-core:6.2.1 (*)
|    +--- org.springframework.boot:spring-boot-test-autoconfigure:3.4.1
|    |    +--- org.springframework.boot:spring-boot:3.4.1 (*)
|    |    +--- org.springframework.boot:spring-boot-test:3.4.1 (*)
|    |    \--- org.springframework.boot:spring-boot-autoconfigure:3.4.1 (*)
|    +--- com.jayway.jsonpath:json-path:2.9.0
|    |    +--- net.minidev:json-smart:2.5.0 -> 2.5.1
|    |    |    \--- net.minidev:accessors-smart:2.5.1
|    |    |         \--- org.ow2.asm:asm:9.6
|    |    \--- org.slf4j:slf4j-api:2.0.11 -> 2.0.16
|    +--- jakarta.xml.bind:jakarta.xml.bind-api:4.0.2
|    |    \--- jakarta.activation:jakarta.activation-api:2.1.3
|    +--- net.minidev:json-smart:2.5.1 (*)
|    +--- org.assertj:assertj-core:3.26.3
|    |    \--- net.bytebuddy:byte-buddy:1.14.18 -> 1.15.11
|    +--- org.awaitility:awaitility:4.2.2
|    |    \--- org.hamcrest:hamcrest:2.1 -> 2.2
|    +--- org.hamcrest:hamcrest:2.2
|    +--- org.junit.jupiter:junit-jupiter:5.11.4
|    |    +--- org.junit:junit-bom:5.11.4
|    |    |    +--- org.junit.jupiter:junit-jupiter:5.11.4 (c)
|    |    |    +--- org.junit.jupiter:junit-jupiter-api:5.11.4 (c)
|    |    |    +--- org.junit.jupiter:junit-jupiter-engine:5.11.4 (c)
|    |    |    +--- org.junit.jupiter:junit-jupiter-params:5.11.4 (c)
|    |    |    +--- org.junit.platform:junit-platform-engine:1.11.4 (c)
|    |    |    +--- org.junit.platform:junit-platform-launcher:1.11.4 (c)
|    |    |    \--- org.junit.platform:junit-platform-commons:1.11.4 (c)
|    |    +--- org.junit.jupiter:junit-jupiter-api:5.11.4
|    |    |    +--- org.junit:junit-bom:5.11.4 (*)
|    |    |    +--- org.opentest4j:opentest4j:1.3.0
|    |    |    \--- org.junit.platform:junit-platform-commons:1.11.4
|    |    |         \--- org.junit:junit-bom:5.11.4 (*)
|    |    +--- org.junit.jupiter:junit-jupiter-params:5.11.4
|    |    |    +--- org.junit:junit-bom:5.11.4 (*)
|    |    |    \--- org.junit.jupiter:junit-jupiter-api:5.11.4 (*)
|    |    \--- org.junit.jupiter:junit-jupiter-engine:5.11.4
|    |         +--- org.junit:junit-bom:5.11.4 (*)
|    |         +--- org.junit.platform:junit-platform-engine:1.11.4
|    |         |    +--- org.junit:junit-bom:5.11.4 (*)
|    |         |    +--- org.opentest4j:opentest4j:1.3.0
|    |         |    \--- org.junit.platform:junit-platform-commons:1.11.4 (*)
|    |         \--- org.junit.jupiter:junit-jupiter-api:5.11.4 (*)
|    +--- org.mockito:mockito-core:5.14.2
|    |    +--- net.bytebuddy:byte-buddy:1.15.4 -> 1.15.11
|    |    +--- net.bytebuddy:byte-buddy-agent:1.15.4 -> 1.15.11
|    |    \--- org.objenesis:objenesis:3.3
|    +--- org.mockito:mockito-junit-jupiter:5.14.2
|    |    +--- org.mockito:mockito-core:5.14.2 (*)
|    |    \--- org.junit.jupiter:junit-jupiter-api:5.11.2 -> 5.11.4 (*)
|    +--- org.skyscreamer:jsonassert:1.5.3
|    |    \--- com.vaadin.external.google:android-json:0.0.20131108.vaadin1
|    +--- org.springframework:spring-core:6.2.1 (*)
|    +--- org.springframework:spring-test:6.2.1 (*)
|    \--- org.xmlunit:xmlunit-core:2.10.0
\--- org.junit.platform:junit-platform-launcher -> 1.11.4
     +--- org.junit:junit-bom:5.11.4 (*)
     \--- org.junit.platform:junit-platform-engine:1.11.4 (*)

(c) - A dependency constraint, not a dependency. The dependency affected by the constraint occurs elsewhere in the tree.
(*) - Indicates repeated occurrences of a transitive dependency subtree. Gradle expands transitive dependency subtrees only once per project; repeat occurrences only display the root of the subtree, followed by this annotation.     

A web-based, searchable dependency report is available by adding the --scan option.

BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed

 

분명 있는데 이새끼이거 뭐야............................................

챗지선생 도와주쇼....

챗지선생에게 물어봤는데, 내게 이것저것 시켜보더니 JUnit5 가 제대로 적용되지 않은거같다며 몇 개 방법을 추천해줬다(의존성은 잘 주입되어있음).

✅ 1️⃣ @BeforeAll은 static 메서드에서만 동작

문제

현재 코드에서는 @BeforeAll을 static 메서드에서 사용하고 있으므로 문제는 없어 보이지만, JUnit 5가 정상적으로 동작하지 않으면 @BeforeAll이 실행되지 않을 수 있음.

해결책

  1. JUnit 4의 @BeforeClass를 import하지 않았는지 확인
    • import org.junit.BeforeClass;가 있으면 안 됨. (JUnit 4용)
    • JUnit 5에서는 @BeforeClass가 존재하지 않음 → @BeforeAll만 사용 가능
  2. JUnit 5가 정상적으로 실행되고 있는지 확인
    • @Test를 추가하여 테스트 실행이 되는지 확인
    • @BeforeAll 메서드가 실행되지 않으면 JUnit 5가 정상적으로 동작하지 않을 가능성이 높음
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class BookValidationTests {
    private static Validator validator;

    @BeforeAll
    static void setUp() {
        System.out.println("BeforeAll 실행됨!");  // ✅ 실행 확인을 위한 로그 추가
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    void testDummy() {
        System.out.println("테스트 실행됨!");  // ✅ JUnit 5 실행 확인
    }
}

ㅎㅎ.....

C:\Y\Cloud Native Spring in Action\catalog-service>gradlew.bat test --tests com.polarbookshop.catalogservice.domain.BookValidationTests

> Task :compileJava
C:\Y\Cloud Native Spring in Action\catalog-service\src\main\java\com\polarbookshop\catalogservice\domain\BookValidationTests.java:6: error: package org.junit.jupiter.api does not exist
import org.junit.jupiter.api.BeforeAll;
                            ^
C:\Y\Cloud Native Spring in Action\catalog-service\src\main\java\com\polarbookshop\catalogservice\domain\BookValidationTests.java:12: error: cannot find symbol
    @BeforeAll
     ^
  symbol:   class BeforeAll
  location: class BookValidationTests
C:\Y\Cloud Native Spring in Action\catalog-service\src\main\java\com\polarbookshop\catalogservice\domain\BookValidationTests.java:19: error: cannot find symbol
    @Test
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler output below.
  C:\Y\Cloud Native Spring in Action\catalog-service\src\main\java\com\polarbookshop\catalogservice\domain\BookValidationTests.java:6: error: package org.junit.jupiter.api does not exist
  import org.junit.jupiter.api.BeforeAll;
                              ^
  C:\Y\Cloud Native Spring in Action\catalog-service\src\main\java\com\polarbookshop\catalogservice\domain\BookValidationTests.java:12: error: cannot find symbol
      @BeforeAll
       ^
    symbol:   class BeforeAll
    location: class BookValidationTests
  C:\Y\Cloud Native Spring in Action\catalog-service\src\main\java\com\polarbookshop\catalogservice\domain\BookValidationTests.java:19: error: cannot find symbol
      @Test
       ^
    symbol:   class Test
    location: class BookValidationTests
  3 errors

* Try:
> Check your code and dependencies to fix the compilation error(s)
> Run with --scan to get full insights.

BUILD FAILED in 7s
1 actionable task: 1 executed

안됨ㅎㅎ........

왜이래 나한테

 

JUnit 패키지를 찾을 수 없는 문제 해결 (JUnit 라이브러리 누락)

📌 org.junit.jupiter.api.BeforeAll을 찾을 수 없다는 에러JUnit 5 라이브러리가 프로젝트에 제대로 추가되지 않았기 때문입니다.
또한 @Test 어노테이션도 찾을 수 없다는 점에서, 전체적인 JUnit 5 설정이 안 되어 있을 가능성이 큽니다.


🔹 해결 방법

1️⃣ testImplementation을 다시 추가

📌 JUnit 5가 build.gradle에 제대로 포함되어 있는지 확인하세요.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    
    // JUnit 5 테스트 의존성
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    
    // JUnit 5 추가 라이브러리 (필요할 경우)
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

📌 testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' 추가하면 더 확실합니다.
(스프링 부트 3.4.1의 기본 spring-boot-starter-test에는 JUnit 5가 포함되어 있지만, 직접 추가하는 것이 더 안정적입니다.)


2️⃣ gradlew clean build 실행 후 다시 테스트

라이브러리 캐시를 지우고 다시 빌드해보세요.

gradlew.bat clean build

 

📌 이후 다시 실행

gradlew.bat test --tests com.polarbookshop.catalogservice.domain.BookValidationTests

 

여기까지했을때.... 문제를 알아차렸다, 내가 이 클래스 파일을 main에 만들어버림.test로 옮겨서 실행하니 잘됨 ^^...오늘 또 뻘짓했다 휴

 

gradlew.bat test --info 로 로깅해둔 거 찍히는 지 확인

정상출력

 


package com.polarbookshop.catalogservice.domain;

import jakarta.validation.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Set;

//Book 객체의 유효성 검사 제약조건을 검증하기 위한 단위 테스트
public class BookValidationTests {
    private static Validator validator;

    // @BeforeAll: 클래스 내의 테스트를 실행하기 전에 가장 먼저 실행할 코드 블록임을 나타냄.
    @BeforeAll
    static void setUp() {
        System.out.println("BeforeAll 실행됨!");
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test // 테스트 케이스임을 나타냄
    void whenAllFieldsCorrectThenValidationSucceeds() {
        System.out.println("정상 데이터 테스트 케이스 실행");
        var book = new Book("0104567890", "제목쏼라쏼라", "김작가",9.80);
        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        assertThat(violations).isEmpty(); // 유효성 검사에서 오류 없음 확인
    }

    @Test // 테스트 케이스임을 나타냄
    void whenIsbnDefinedIncorrectThenValidationFails() {
        System.out.println("비정상 데이터 테스트 케이스 실행");
        // 유효하지않은 ISBN 입력
        var book = new Book("ab45678d90", "제목쏼라", "정작가",8.70);

        // book 객체의 유효성을 검사하고, 제약 조건을 위반한 항목들을 Set<ConstraintViolation<Book>>에 저장.
        Set<ConstraintViolation<Book>> violations = validator.validate(book);
        // 오류가 1개인지 확인
        assertThat(violations).hasSize(1);
        // 만약 getMessage()의 실제 값이 "The book ISBN must be defined"이면, 테스트는 실패
        assertThat(violations.iterator().next().getMessage()).isEqualTo("The ISBN format must be valid.");
    }
}

최종소스.

돌려본다...!

 

점찍어서 테스트 실패함 ㅅㅂ ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

 

 

암튼 점지우니 성공 ^^.....이야...... 절겁다..... 


+ 추가 지식

📌 ConstraintViolation<T>란?

ConstraintViolation<T>는 Jakarta Validation API에서 제공하는 인터페이스로, 유효성 검사(Validation)에서 발생한 오류 정보를 저장하는 객체입니다.
즉, 어떤 필드가 어떤 이유로 제약 조건을 위반했는지에 대한 정보를 담고 있음.


기본 개념

  • T는 유효성 검사를 실행한 객체의 타입을 나타냅니다.
  • ConstraintViolation<T> 객체는 다음 정보를 포함합니다.
    1. 어떤 필드에서 오류가 발생했는지 (getPropertyPath())
    2. 어떤 제약 조건이 위반되었는지 (getConstraintDescriptor())
    3. 실제 입력 값이 무엇인지 (getInvalidValue())
    4. 오류 메시지가 무엇인지 (getMessage())