Spring Boot 기준으로 JUnit 5(Jupiter) 를 사용하는 백엔드 테스트 스위트 구성을 “바로 복붙 가능한” 예제로 정리했습니다. (Java 예제지만, Kotlin도 동일한 구조·어노테이션으로 동작합니다.)
---
1) 기본 의존성 & 디렉터리
Maven (pom.xml)
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test (MockMvc, @SpringBootTest 등 포함) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<!-- Vintage 제외 (JUnit4 미사용 시) -->
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Mockito (선택: 명시적 사용 시) -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.13.0</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers (통합 테스트용, DB는 예시로 Postgres) -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Unit 테스트: surefire / 통합 테스트: failsafe 권장 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<useModulePath>false</useModulePath>
<!-- 태그 기반 실행 예시: 기본은 unit만 -->
<!-- <groups>unit</groups> -->
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<!-- 통합 테스트만 돌리고 싶으면 integration 태그 지정 -->
<groups>integration</groups>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
디렉터리 레이아웃
src
├─ main/java/... (프로덕션 코드)
└─ test/java/...
├─ unit/... (순수 단위 테스트: @Tag("unit"))
├─ slice/web/... (@WebMvcTest)
├─ slice/jpa/... (@DataJpaTest)
├─ integration/... (@SpringBootTest + Testcontainers: @Tag("integration"))
└─ suite/... (JUnit Platform Suite 모음)
---
2) 프로파일 & 테스트 DB
src/test/resources/application-test.yml (통합/슬라이스 공용 테스트 설정)
spring:
jpa:
hibernate:
ddl-auto: create-drop
datasource:
# @DataJpaTest는 H2 자동 구성 가능, Testcontainers 사용 시 동적으로 주입
jackson:
serialization:
write-dates-as-timestamps: false
> 통합 테스트는 Testcontainers가 제공하는 JDBC URL로 덮어씁니다.
---
3) 단위(Unit) 테스트 (Mockito)
src/test/java/.../unit/UserServiceTest.java
package com.example.unit;
import com.example.user.User;
import com.example.user.UserRepository;
import com.example.user.UserService;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyLong;
@Tag("unit")
class UserServiceTest {
private final UserRepository repo = Mockito.mock(UserRepository.class);
private final UserService service = new UserService(repo); // 생성자 주입 가정
@Test
void findById_returnsUser() {
Mockito.when(repo.findById(anyLong()))
.thenReturn(Optional.of(new User(1L, "jgkim")));
User u = service.findById(1L);
assertThat(u.getName()).isEqualTo("jgkim");
Mockito.verify(repo).findById(1L);
}
}
---
4) Web 슬라이스(@WebMvcTest) — 컨트롤러만 빠르게
src/test/java/.../slice/web/UserControllerWebTest.java
package com.example.slice.web;
import com.example.user.UserController;
import com.example.user.UserService;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@Tag("web")
@WebMvcTest(controllers = UserController.class)
class UserControllerWebTest {
@Autowired
MockMvc mockMvc;
@MockBean
UserService userService;
@Test
void getUser_returns200() throws Exception {
Mockito.when(userService.getName(1L)).thenReturn("jgkim");
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("jgkim"));
}
}
---
5) JPA 슬라이스(@DataJpaTest) — Repository만 빠르게
src/test/java/.../slice/jpa/UserRepositoryDataJpaTest.java
package com.example.slice.jpa;
import com.example.user.User;
import com.example.user.UserRepository;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import static org.assertj.core.api.Assertions.assertThat;
@Tag("jpa")
@DataJpaTest
class UserRepositoryDataJpaTest {
@Autowired
UserRepository repository;
@Test
void save_and_find() {
User saved = repository.save(new User(null, "jgkim"));
assertThat(saved.getId()).isNotNull();
assertThat(repository.findById(saved.getId()))
.get()
.extracting(User::getName)
.isEqualTo("jgkim");
}
}
---
6) 통합(Integration) 테스트 — Testcontainers
src/test/java/.../integration/DatabaseIntegrationTest.java
package com.example.integration;
import com.example.user.User;
import com.example.user.UserRepository;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
@Tag("integration")
@Testcontainers
@SpringBootTest
@ActiveProfiles("test")
class DatabaseIntegrationTest {
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@BeforeAll
static void start() {
postgres.start();
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
System.setProperty("spring.datasource.username", postgres.getUsername());
System.setProperty("spring.datasource.password", postgres.getPassword());
}
@AfterAll
static void stop() {
postgres.stop();
}
@Autowired
UserRepository repository;
@Test
void end_to_end_save_and_load() {
User u = repository.save(new User(null, "container-user"));
assertThat(repository.findById(u.getId())).get()
.extracting(User::getName).isEqualTo("container-user");
}
}
> 필요 시 @DynamicPropertySource 로 DataSource 속성을 주입해도 됩니다.
---
7) 스위트 구성(실행 묶음)
JUnit5는 전통적 @RunWith(Suite.class) 대신 JUnit Platform Suite 를 사용합니다.
원하는 태그/패키지를 기준으로 테스트 스위트를 정의합니다.
src/test/java/.../suite/SmokeSuite.java
package com.example.suite;
import org.junit.platform.suite.api.*;
@Suite
@IncludeTags({"unit","web"})
@SelectPackages({
"com.example.unit",
"com.example.slice.web"
})
public class SmokeSuite { }
src/test/java/.../suite/FullSuite.java
package com.example.suite;
import org.junit.platform.suite.api.*;
@Suite
@SelectPackages("com.example")
@ExcludeTags("slow") // 느린 테스트 제외 등
public class FullSuite { }
> CI에서는 태그 기반 실행이 더 유연합니다.
단위만: mvn -Dgroups=unit test (Surefire)
통합만: mvn -Dgroups=integration verify (Failsafe)
---
8) 파라미터/계층/조건부 실행 — 실전 팁
ParameterizedTest
@ParameterizedTest
@CsvSource({"admin,true", "guest,false"})
void canAccess(String role, boolean expected) {
boolean actual = authService.canAccess(role);
assertThat(actual).isEqualTo(expected);
}
@Nested 로 시나리오 가독성 향상
@Nested
class WhenResourceExists {
@Test void returns200() { /* ... */ }
}
조건부 실행
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
@Test void onlyOnCI() { }
Fixture/TestDataBuilder
class UserBuilder {
Long id; String name="default";
UserBuilder id(Long v){ this.id=v; return this; }
UserBuilder name(String v){ this.name=v; return this; }
User build(){ return new User(id, name); }
}
---
9) CI 예시 (GitHub Actions)
.github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
ports: ["5432:5432"]
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Unit/Web/JPA (fast)
run: mvn -B -Dgroups="unit,web,jpa" test
- name: Integration (Testcontainers)
run: mvn -B -Dgroups="integration" verify
---
10) 운영 팁 체크리스트
테스트 분리: unit(초고속) ↔ integration(느림) 태그로 분리
슬라이스 테스트 적극 활용: @WebMvcTest, @DataJpaTest 로 범위 축소
Testcontainers 재사용: 로컬 개발 시 Ryuk/이미지 캐시로 속도 개선
고정 포트/시드 데이터 피하기: 병렬 실행 안정성 확보
Fixture/Builder 패턴: 테스트 가독성과 유지보수성 향상
실행 속도 가드레일: 1분 내 smoke, 5~10분 내 full
---
필요하시면 Kotlin 버전, 멀티 모듈(MSA)용 스위트 템플릿, Jenkins 파이프라인 예시도 바로 만들어 드릴게요.
'skill > check' 카테고리의 다른 글
| 테스트 스위트(Test Suite) (0) | 2025.10.14 |
|---|---|
| 셀레니움(Selenium) 단점 (0) | 2025.10.14 |
| 셀레니움(Selenium) React 페이지 데이터 수집 예제 (Headless 모드) (0) | 2025.10.14 |
| 셀레니움(Selenium) (0) | 2025.10.14 |