1. 단위 테스트(Unit Test)의 개념과 환경 개요
- 단위 테스트는 메인 소스 코드(src/main/java)와 완전히 격리된 별도의 영역인 src/test/java 폴더에서 수행되는 품질 검증 단계이다. (품질 보증 및 자동화)
- 즉, 외부 시스템(DB, 외부 API 등)에 의존하지 않고, 하나의 기능만을 독립적으로 격리하여 테스트하는 것을 의미.
- 독립적인 검증: 외부 시스템(웹 서버, 데이터베이스 등)에 의존하지 않고, 하나의 메서드나 클래스의 기능만을 분리하여 테스트.
- 실행 및 자동화: IDE(IntelliJ, Eclipse 등)에서 JUnit Test를 통해 손쉽게 실행하며, 런타임 시점에 에러가 없는지 검증. 각 테스트 메서드가 하나의 독립된 테스트 단위가 됨.
- 폴더구조(src/test/java): Java 프로젝트에서 src/main/java가 실제 서비스 코드가 움직이는 영역이라면, src/test/java는 테스트 코드가 안전하게 작동하는지 검증하는 방패 역할을 한다.
2. 테스트 프레임워크의 이해: JUnit 5와 Mockito
효율적인 테스트 자동화를 위해 스프링 프레임워크와 독립된 별도의 테스팅 도구들을 활용한다.
1) JUnit Jupiter (JUnit 5)
- JUnit 4와는 완전히 다른, JUnit 5 버전의 새로운 핵심 테스팅 엔진 프레임워크. (Spring Framework와는 독립적)
- 실행: 테스트 폴더에서 Run As ▶️ JUnit Test
- 구버전 코드 호환성이 필요할 땐 아래 Vintage 엔진이 함께 쓰임.
- JUnit Vintage (하위 호환): JUnit 3, 4로 작성된 과거의 테스트 코드를 JUnit 5 환경에서도 그대로 실행할 수 있도록 지원하는 마이그레이션 엔진.
2) Mockito (목업 도구)
- 단위 테스트를 빠르고 안전하게 자동화: 가짜 브라우저나 가짜 객체(Mock)를 만들어 실제 가동 중인 웹 서버나 DB에 연결하지 않고도, 데이터를 시뮬레이션할 수 있게 돕는 필수 라이브러리.
- 단위 테스트를 가볍고 빠르게 자동화하기 위한 필수적인 도구.
2. 환경 구축을 위한 의존성(Dependencies) 설정
JUnit 5와 테스트에 필요한 로깅 라이브러리를 설정해야 함. (테스트 코드의 실행 환경과 검증 도구를 제공)
더보기
ℹ️ 스타터(Starter) 내부 로깅 의존성에 대하여
1) 스타터(Starter) 내부의 로깅 의존성 구조
- 스프링 부트의 모든 웹, 테스트, 메인 스타터 의존성(spring-boot-starter-web, spring-boot-starter-test 등)은 핵심 기반이 되는 spring-boot-starter를 공통으로 포함하고 있음
- 스타터 시리즈 내부 탑재 (spring-boot-starter-logging): 스타터 의존성을 쓰면 기본적으로 로깅 시스템(SLF4J, Logback)이 내장되음 .
- 이 덕분에 build.gradle이나 pom.xml에 로깅 관련 라이브러리를 별도로 추가하지 않아도 곧바로 콘솔에서 정돈된 로그를 볼 수 있다.
[spring-boot-starter-web] 또는 [spring-boot-starter-test]
└── [spring-boot-starter]
└── [spring-boot-starter-logging] <-- 로깅의 핵심!
├── slf4j-api (인터페이스)
└── logback-classic (구현체)
2) 왜 하필 SLF4J와 Logback의 조합일까?
현재 자바 생태계에서 가장 표준적으로 사용되는 추상화(인터페이스)와 구현체의 조합이기 때문.
- SLF4J (Simple Logging Facade for Java): 로깅을 직접 수행하는 라이브러리가 아니라, 로깅 프레임워크들을 통일된 방식으로 사용할 수 있게 해주는 인터페이스(껍데기).
- 코드를 작성할 때는 org.slf4j.Logger를 사용해 표준화된 방식으로 작성하므로, 나중에 로깅 엔진을 바꾸더라도 소스 코드를 한 줄도 수정할 필요가 없음.
- Logback:
- SLF4J의 기능을 실제로 구현한 로깅 엔진(구현체). 과거에 많이 쓰이던 Log4j의 후속작으로, 속도가 매우 빠르고 메모리 효율이 뛰어남.
3) 기본 콘솔 출력의 특징
스프링 부트는 아무런 설정을 하지 않아도 가독성이 높은 기본 콘솔 로그 포맷을 제공한다.
- 기본 로그 레벨: 기본적으로 INFO 레벨 이상(INFO, WARN, ERROR)의 로그만 콘솔에 출력되도록 필터링되어 있음. (DEBUG, TRACE 등 너무 상세한 로그는 기본적으로 숨겨짐)
- 제공되는 정보: 단순한 문자열만 찍히는 System.out.println()과 달리, [발생 시간], [로그 레벨], [프로세스 ID], [스레드 이름], [클래스명]이 자동으로 함께 기록되므로 디버깅과 추적이 압도적으로 유리.
2026-06-27 14:51:05.123 INFO 12345 --- [main] com.example.myapp.MyApplication : Started MyApplication...
4) 요약 및 팁
- 스프링 부트 스타터를 쓰면 SLF4J(인터페이스) + Logback(구현체) 조합이 자동으로 세팅된다.
- 만약 기본 INFO 레벨 대신 더 상세한 DEBUG 로그를 보고 싶다면, application.properties나 application.yml 파일에 logging.level.root=DEBUG 같은 간단한 설정만 추가해주면 됨.
1) build.gradle 파일에 아래 설정을 추가 (Gradle)
dependencies {
// JUnit 5 및 테스트 관련 도구 포함 (Spring Boot 프로젝트 기준)
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Spring Boot를 쓰지 않는 순수 Java 프로젝트라면 아래와 같이 추가.
// testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
// testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
}
tasks.named('test') {
useJUnitPlatform() // JUnit 5를 사용하기 위한 필수 설정
}
2) pom.xml 파일에 의존성 추가 (Maven)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3. 테스트 환경만을 위한 로깅(Logging) 설정
테스트를 실행할 때, 콘솔창에 불필요한 디버그 로그가 너무 많이 찍히거나 반대로 필요한 로그가 보이지 않으면 디버깅이 어려움.
src/test/resources 폴더 안에 테스트 전용 로그 설정 파일을 만들어 관리하는 것이 좋다.
1) src/test/resources/logback-test.xml 생성 (환경 세팅)
- Logback은 logback-test.xml 파일이 있으면 일반 logback.xml보다 우선적으로 로드한다.
- 로그를 어떤 모양(포맷)으로, 어느 깊이(레벨)까지 출력할지 규칙을 정할 수 있음
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-history HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.example.myapp" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
2) @Slf4j와 롬복(Lombok) 연동 (롬복 의존성 추가)
- 롬복의 @Slf4j 어노테이션을 사용하면 IDE가 자동으로 로깅 객체를 생성하고 추가해 주므로 편하게 로그를 남길 수 있음.
- 롬복 의존성을 추가할 때 <scope> 태그를 누락하지 않거나(기본값), 빌드 플러그인 설정이 필요. 일반적으로 Spring Boot 환경에서는 아래와 같이 기본 의존성을 추가해 주면 테스트 코드에서도 롬복을 바로 사용할 수 있음
- 테스트 코드 안에서 롬복 어노테이션을 해석할 수 있도록 컴파일러에게 쥐여줌
// build.gradle 파일에 추가
dependencies {
// ... 기존 의존성들 ...
// 테스트 환경에서 롬복을 사용하기 위한 설정
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
<!-- pom.xml 파일에 추가 -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3) ⚠️ @Slf4j와 롬복 로그 중복 출력 현상 원인 분석
테스트 실행 시 콘솔에 동일한 로그가 두 번씩 찍히는 현상이 발생한다면 다음과 같은 원인을 점검해야 한다. (롬복 자체의 버그보다는 구조 문제일 가능성이 높다)
- 테스트 메서드 중복 실행: contextLoads(), contextLoads1()처럼 유사한 테스트 메서드가 여러 개 존재하여 각각 실행되면서 로그가 여러 번 출력되는 경우.
- Logback 설정 중복 (additivity): 로깅 설정 파일 (logback-spring.xml 등) 에서 상위 로거(Root)와 하위 로거의 additivity(상속 여부) 속성이 기본값(true)으로 설정되어 콘솔에 중복 출력되는 경우.
- Tip: 하위 로거 설정에 additivity="false"를 지정하면 중복 출력을 방지할 수 있음.
ℹ️ 로그 출력 검증: 테스트 메서드(contextLoads) 실행 시 Logback을 통해 콘솔에 로깅 문자열을 기록하고 에러 유무를 확인하는 과정.
4. 첫 번째 단위 테스트 코드 작성하기 (+로깅)
이제 src/test/java 아래에 실제 테스트 클래스를 생성. 테스트 대상 클래스의 패키지 구조와 동일하게 맞추는 것이 관례.
1) SLF4J 로거 사용
package com.example.myapp;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;
class CalculatorTest {
// SLF4J 로거 선언
private static final Logger log = LoggerFactory.getLogger(CalculatorTest.class);
@Test
@DisplayName("두 숫자의 합을 올바르게 반환해야 한다")
void sum_ShouldReturnCorrectValue() {
// Given (준비)
int a = 10;
int b = 20;
log.info("테스트 시작 - 입력값: a={}, b={}", a, b);
// When (실행)
int result = a + b; // 원래는 Calculator.add(a, b) 형태가 되겠죠?
log.debug("계산 완료 - 결과값: {}", result);
// Then (검증)
assertThat(result).isEqualTo(30);
log.info("테스트 성공적 종료");
}
}
2) 롬복의 @Slf4j 어노테이션 사용 (권장)
- IDE가 자동으로 로깅 객체를 생성하고 추가해 줌. 매번 클래스마다 LoggerFactory.getLogger()를 선언할 필요 없이 편하게 로그를 남길 수 있다.
package com.example.myapp;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import lombok.extern.slf4j.Slf4j; // 1. 롬복 Slf4j 임포트
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j // 2. 클래스 상단에 어노테이션 추가 (자동으로 log 객체 생성)
class LombokLoggerTest {
@Test
@DisplayName("롬복 @Slf4j를 활용한 테스트 로깅")
void lombokLogTest() {
String testName = "Lombok Test";
// 3. 선언 없이 바로 'log' 변수 사용 가능
log.info("롬복 라이브러리를 이용한 로그 출력 시작: {}", testName);
int value = 100;
log.debug("디버깅용 데이터 확인: value = {}", value);
assertThat(value).isEqualTo(100);
log.info("롬복 로그 테스트 성공적으로 완료");
}
}
5. 효과적인 테스트 로깅을 위한 3가지 팁
- src/test/resources 활용하기
- 테스트 실행 시 필요한 설정값(application.yml 등)이나 로깅 설정은 반드시 src/test 하위 폴더에 별도로 두기. 메인 서비스 환경을 오염시키지 않을 수 있다.
- 로그 레벨 적절히 활용하기
- 테스트의 흐름을 파악할 때는 INFO, 파라미터나 중간 연산 값을 세부적으로 출력할 때는 DEBUG 레벨을 사용하여 콘솔이 지저분해지는 것을 막음.
- System.out.println() 대신 Logger 쓰기 (롬복 log.info())
- System.out은 비효율적일 뿐만 아니라 로그 포맷(시간, 스레드 등)을 제공하지 않음. 나중에 빌드 서버(CI/CD)에서 로그를 추적할 때를 대비해 항상 SLF4J 로거를 사용하는 것이 좋다.
- 테스트 기본 규칙: 가능하면 *@Test만 사용하기
- 테스트 속도가 빨라야 개발 피드백을 자주 받을 수 있기 때문에, 가능하면 @Test만 사용하는 단위 테스트를 최대한 많이 작성하는 게 좋음.
- 통합 테스트가 필요할 때: DB 연동이나 외부 API 호출 등 진짜 스프링의 기능이 잘 엮여서 도는지 확인해야 할 때만 @SpringBootTest를 사용.
- 중간 지점 슬라이스 테스트: 만약 컨트롤러 레이어만 테스트하고 싶다거나 JPA 리포지토리만 테스트하고 싶다면, 전체를 다 띄우는 @SpringBootTest 대신 @WebMvcTest나 @DataJpaTest 같은 슬라이스 테스트 어노테이션을 사용하는 것이 효율적.
더보기
✨ 테스트 코드에 사용되는 두가지 어노테이션 비교
1) @Test (JUnit 5)
- 특징: 스프링 컨테이너(Bean들을 모아둔 공간)를 전혀 로드하지 않음.
- 장점: 매우 가볍고 실행 속도가 빠름 (밀리초 단위).
- 주요 용도: 단위 테스트 (Unit Test)
- 어떤 비즈니스 로직(예: 계산기 기능, 특정 문자열 파싱 등)이 순수하게 잘 작동하는지 클래스/메서드 단위로 확인할 때 씀.
- 외부 의존성(데이터베이스 등)이 필요하다면 가짜 객체(Mock)를 만들어서 테스트.
- 의존성 주입 불가능
2) @SpringBootTest (위 예시엔 없음, 통합 테스트용)
- 특징: 스프링 부트가 제공하는 어노테이션으로, 실제 서비스를 구동할 때처럼 스프링 컨테이너를 통째로 띄움. @Component, @Service, @Repository 등 내가 만든 모든 Bean이 등록.
- 단점: 무거움. 애플리케이션 규모가 커질수록 테스트를 시작하는 데 시간이 오래 걸림(몇 초에서 몇 분까지도 걸림).
- 주요 용도: 통합 테스트 (Integration Test)
- 컨트롤러 ▶️ 서비스 ▶️ 리포지토리 ▶️ 데이터베이스 까지 이어지는 전체적인 흐름이 진짜 제대로 맞물려 돌아가는지 확인할 때 사용.
- 의존성 주입(@Autowired) 가능
- 참고: @SpringBootTest를 쓸 때도, 테스트 메서드 자체에는 JUnit의 @Test를 반드시 붙여주어야 실행이 됨.
'Tech Stack > Backend' 카테고리의 다른 글
| 🛡️Spring Security & HTTP Basic 인증 방식의 이해 (0) | 2026.06.29 |
|---|---|
| 🛡️자바 서블릿 기반의 HttpSession (세션) (0) | 2026.06.29 |
| 🥾디스패처 서블릿 (레거시 방식과 REST API 방식) (0) | 2026.06.25 |
| 🥾 레거시와 Spring Boot 웹 서버 구동 방식 비교 (+ REST API) (0) | 2026.06.25 |
| 🐱순수 Jakarta EE 서블릿 환경에서 톰캣 연동 및 구동 테스트 (0) | 2026.06.24 |