πŸ“š TDD(ν…ŒμŠ€νŠΈ 주도 개발) ν•™μŠ΅ 및 `@SpringBootTest` λΉŒλ“œ 속도 μ €ν•˜ νŠΈλŸ¬λΈ” μŠˆνŒ…

zionΒ·2025λ…„ 10μ›” 31일

ν…ŒμŠ€νŠΈ 주도 개발(Test Driven Development, TDD)은 κΈ°λŠ₯ κ΅¬ν˜„μ— μ•žμ„œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό λ¨Όμ € μž‘μ„±ν•˜λŠ” μ†Œν”„νŠΈμ›¨μ–΄ 개발 λ°©λ²•λ‘ μž…λ‹ˆλ‹€. ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό λ¨Όμ € μž‘μ„±ν•˜κ³  이λ₯Ό ν†΅κ³Όμ‹œν‚¬ μ΅œμ†Œν•œμ˜ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 과정을 λ°˜λ³΅ν•˜λ©° κ°œλ°œμ„ μ§„ν–‰ν•©λ‹ˆλ‹€.


πŸ™‹β€β™€οΈ TDD에 λŒ€ν•œ κ²½ν—˜κ³Ό 의문점

3λ…„ μ „ Spring 곡뢀 쀑 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ ‘ν–ˆκ³ , 이후 μ‚¬μ΄λ“œ ν”„λ‘œμ νŠΈμ—μ„œ PR ν…œν”Œλ¦Ώμ— λ§žμΆ”μ–΄ λ‹¨μˆœ 값을 λŒ€μΆ© λ°”κΏ” λ„£λŠ” μ •λ„λ‘œλ§Œ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. 리뷰 없이 'λ‚΄ λ§˜λŒ€λ‘œ' μž‘μ„±ν–ˆκΈ°μ—, TDD 방식은 μ•Œμ•˜μ§€λ§Œ ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±μ΄ λ‹¨μˆœ 반볡적이고 μž¬λ―Έμ—†λŠ” 일둜 λŠκ»΄μ‘ŒμŠ΅λ‹ˆλ‹€. 특히 'ν…ŒμŠ€νŠΈ μ½”λ“œλΆ€ν„° 개발'ν•œλ‹€λŠ” 방식이 ν˜„μ‹€μ μœΌλ‘œ μƒμƒλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

TDD 의문점과 ν•΄κ²°

Q1. ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ§œλŠ”λ° μ‹œκ°„μ΄ κ±Έλ¦°λ‹€.

  • ν…ŒμŠ€νŠΈ μ½”λ“œμ— λ“€μ΄λŠ” μ‹œκ°„μ΄ μ•„μ˜ˆ μ—†μ„μˆ˜λŠ” μ—†λ‹€. ν•˜μ§€λ§Œ TDD λ°©μ‹μœΌλ‘œ 검증과 κ°œλ°œμ„ λ™μ‹œμ— μ§„ν–‰ν•œλ‹€λ©΄, 개발만 ν•˜λ‹€κ°€ κ³„νšμ„ λ³€κ²½ν•˜κ³ , μ‚¬μ΄λ“œ μ΄νŽ™νŠΈλ₯Ό κ²½ν—˜ν•˜λŠ” 것보닀 λΉ λ₯Όμˆ˜λ°–에 μ—†λ‹€.

Q2. ν…ŒμŠ€νŠΈ μ½”λ“œλΆ€ν„° κ°œλ°œν•˜λŠ” 상황이 상상이 λ˜μ§€ μ•ŠλŠ”λ‹€.

  • ν…Œμ΄λΈ” 섀계λ₯Ό λ¨Όμ € ν•˜λŠ” 것에 μ΅μˆ™ν•˜λ‹€ λ³΄λ‹ˆ, μ „ν˜€ κ·Έλ €μ§€μ§€ μ•ŠλŠ” μΌμ΄μ—ˆλ‹€. ν•˜μ§€λ§Œ ν•„μš”ν•œ APIμš”μ²­λ“€μ„ E2E둜 λ¨Όμ € μž‘μ„±ν•˜κ³ , μ„Έμ„Έν•œ λ‘œμ§λ“€μ„ λ§Œλ“€μ–΄ κ°€λŠ” κ³Όμ •, 객체의 νŠΉμ„±μ„ λ…Ήμ΄λŠ” 과정이 μ΄μ œλŠ” μ΄ν•΄λ˜κΈ° μ‹œμž‘ν–ˆλ‹€.

Q3. λ‹¨μœ„/톡합/E2E 각각 μ–΄λ–€ 검증을 ν•΄μ•Όν•˜λ‚˜μš”.

λ‹¨μœ„/톡합 ν…ŒμŠ€νŠΈ (주둜 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 및 객체 μƒνƒœ 검증)

//DB μ €μž₯ λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ˜λŠ”μ§€ 확인
verify(repository).save(any())
//λ°˜ν™˜λœ 객체가 null이 μ•„λ‹Œμ§€
assertNotNull(result)
//λ°˜ν™˜ 객체의 핡심 ν•„λ“œκ°€ μ •ν™•ν•œμ§€ 
assertEquals(expectedValue, result.getField())
//νŠΉμ • ν•„λ“œ 값이 λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— λ§žλŠ”μ§€
assertTrue(result.isValid())
//μ‚­μ œ λ©”μ„œλ“œκ°€ μ •ν™•νžˆ 1번 ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€ 확인
verify(repository, times(1)).deleteById(id)
//μ‚­μ œ ν›„ 쑰회 μ‹œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜λŠ”μ§€ 확인
assertThrows(NotFoundException.class, () -> service.read(id))

E2E ν…ŒμŠ€νŠΈ (주둜 μ‹œμŠ€ν…œ 응닡 및 μ˜μ†μ„± 검증)

//201 응닡 확인
assertEquals(HttpStatus.CREATED, response.getStatusCode())
//DBμ—μ„œ 직접 μ‘°νšŒν•˜μ—¬ 객체가 μ‘΄μž¬ν•˜λŠ”μ§€ 확인.
assertNotNull(repository.findById(id).orElse(null))
//응닡 JSON ν•„λ“œ κ°’ 검증
assertEquals(expectedValue, response.getBody().getFieldName())
//DB 쑰회 κ²°κ³Ό Optional이 λΉ„μ–΄μžˆλŠ”μ§€ 확인
assertTrue(repository.findById(id).isEmpty())

Q4. 운영DBμ—μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ μ‹€ν–‰λ μˆ˜λ„ μžˆλ‹€λ˜λ°..

  • 이 μœ„ν—˜λ„ λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±μ΄ μ£Όμ €λ˜κΈ°λ„ ν–ˆλ‹€. 사싀 λ‚΄κ°€ 섀정을 잘 ν•΄λ†“μœΌλ©΄ λ˜λŠ” 일인데, ν˜Ήμ—¬λ‚˜ ν•˜λŠ” 걱정이 μžˆμ—ˆλ‹€.

🐒 @SpringBootTest λΉŒλ“œ 속도 μ €ν•˜ νŠΈλŸ¬λΈ” μŠˆνŒ…

E2E 및 톡합 ν…ŒμŠ€νŠΈ 개발 쀑 λΉŒλ“œ μ‹œκ°„μ΄ 5λΆ„ 이상 μ†Œμš”λ˜λŠ” ν˜„μƒμ΄ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. 원인을 μ°ΎκΈ° μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€.

1) @Spy와 @SpyBean 차이점 κ²€ν†  (원인 μ•„λ‹˜)

νŠΉμ§•@Spy (Mockito)@SpyBean (Spring Boot Test)
ν”„λ ˆμž„μ›Œν¬MockitoSpring Boot Test
μ‚¬μš© ν™˜κ²½λ‹¨μœ„ ν…ŒμŠ€νŠΈ (Unit Test)톡합 ν…ŒμŠ€νŠΈ (Integration Test)
객체 λŒ€μƒκ°œλ°œμžκ°€ new둜 μƒμ„±ν•œ 일반 μΈμŠ€ν„΄μŠ€Spring μ»¨ν…μŠ€νŠΈ λ‚΄μ˜ κΈ°μ‘΄ Bean
μ£Όμž… 방식@InjectMocksλ₯Ό ν†΅ν•œ μˆ˜λ™ μ£Όμž…@Autowiredλ₯Ό ν†΅ν•œ Spring의 μžλ™ μ£Όμž…
영ν–₯ λ²”μœ„ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ/클래슀 λ‚΄λΆ€Spring Application Context 전체

@SpyBean μ‚¬μš© μ‹œμ κ³Ό 속도 μ €ν•˜ μ‹œμ μ΄ λΉ„μŠ·ν•˜μ—¬ μ˜μ‹¬ν–ˆμ§€λ§Œ, μ΄λŠ” κ·Όλ³Έ 원인이 μ•„λ‹˜μ„ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.

2) Testcontainers ν™˜κ²½ 문제 발견 (μ£Όμš” 원인)

Docker와 Testcontainersκ°€ λ„μ›Œμ§€λŠ” 둜그λ₯Ό 톡해 Docker κ΄€λ ¨ λ¬Έμ œμž„μ„ μΆ”μ •ν–ˆμŠ΅λ‹ˆλ‹€.

βœ… ν•΄κ²° κ³Όμ •
1) Docker Desktop μ—…λ°μ΄νŠΈ
2) Testcontainers μ•„μ΄μ½˜μ„ λ°œκ²¬ν•˜κ³  μ‹€ν–‰ 쀑인 μ»¨ν…Œμ΄λ„ˆλ₯Ό λͺ¨λ‘ μ‚­μ œν–ˆμŠ΅λ‹ˆλ‹€.
3) Containers running locally > Embedded Runtime을 desktop-linux둜 λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€.
4) μ„€μ • λ³€κ²½ ν›„ ν…ŒμŠ€νŠΈ μ‹€ν–‰ 속도가 ν˜„μ €νžˆ λΉ¨λΌμ‘ŒμŠ΅λ‹ˆλ‹€.

πŸ”Ž Embedded Runtime vs. Desktop Linux Docker 데λͺ¬

μ„±λŠ₯ 및 μ˜€λ²„ν—€λ“œ (M1/Mac ν™˜κ²½μ˜ 경우 더 μ€‘μš”)
Embedded Runtime (λ¦¬λˆ…μŠ€ λ„€μ΄ν‹°λΈŒ): TestcontainersλŠ” 도컀 APIλ₯Ό μ‚¬μš©ν•˜μ—¬ μ»¨ν…Œμ΄λ„ˆλ₯Ό 직접 μ‹€ν–‰ν•©λ‹ˆλ‹€. 파일 μ‹œμŠ€ν…œ 접근이 호슀트 λ¦¬λˆ…μŠ€ 컀널 μˆ˜μ€€μ—μ„œ 이루어지기 λ•Œλ¬Έμ— μ˜€λ²„ν—€λ“œκ°€ 거의 μ—†μ–΄ λΉŒλ“œ 및 ν…ŒμŠ€νŠΈ 속도가 κ°€μž₯ λΉ λ¦…λ‹ˆλ‹€.

Docker Desktop (Mac/Windows): Macμ΄λ‚˜ Windowsμ—μ„œ Docker Desktop을 μ‚¬μš©ν•˜λ©΄, 도컀 데λͺ¬μ€ 항상 λ‚΄λΆ€ VM λ‚΄μ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€. 특히 M1/M2/M3 Mac의 경우, μ„±λŠ₯ κ°œμ„ μ΄ μ΄λ£¨μ–΄μ‘Œμ§€λ§Œ μ—¬μ „νžˆ λ„€μ΄ν‹°λΈŒ λ¦¬λˆ…μŠ€μ— λΉ„ν•˜λ©΄ μ•½κ°„μ˜ 파일 I/O μ˜€λ²„ν—€λ“œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

제 ν™˜κ²½(M1)μ—μ„œλŠ” μ˜ˆμƒκ³Ό 달리 Embedded Runtimeμ—μ„œ 더 였래 κ±Έλ¦¬λŠ” ν˜„μƒμ„ κ²½ν—˜ν–ˆμŠ΅λ‹ˆλ‹€. 이뢀뢄은 쒀더 곡뢀해봐야 직접적인 문제λ₯Ό μ•Œμˆ˜ μžˆμ„κ²ƒ κ°™μŠ΅λ‹ˆλ‹€.

πŸ’‘ TDD κ΄€λ ¨ ν•™μŠ΅ λ‚΄μš© 정리

이번 κ²½ν—˜μ„ 톡해 μ•Œκ²Œ 된 μ£Όμš” κ°œλ…κ³Ό 도ꡬλ₯Ό μ •λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€. μΆ”ν›„ μ ν•©ν•œ 도ꡬλ₯Ό ν™œμš©ν•΄ λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό κ²½ν—˜ν•΄ 보렀고 ν•©λ‹ˆλ‹€.

🟒 ν…ŒμŠ€νŠΈ 더블 (Test Double)

  • Dummy – ν…ŒμŠ€νŠΈμ— ν•„μš”ν•˜μ§€λ§Œ μ‹€μ œλ‘œ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” 더미 객체
  • Stub – νŠΉμ • λ©”μ„œλ“œ 호좜 μ‹œ 미리 μ •μ˜λœ κ°’ λ°˜ν™˜
  • Fake – μ‹€μ œ κ΅¬ν˜„κ³Ό λΉ„μŠ·ν•˜μ§€λ§Œ λ‹¨μˆœν™”λœ 객체 (예: In-memory DB)
  • Mock – 호좜 μ—¬λΆ€, 인자 등을 검증할 수 μžˆλŠ” 객체
  • Spy – μ‹€μ œ 객체λ₯Ό κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜λ©΄μ„œ 일뢀 λ™μž‘λ§Œ κ°μ‹œ/검증

πŸ§ͺ E2E ν…ŒμŠ€νŠΈ

  • TestRestTemplate – Spring Boot μ„œλ²„ λ„μ›Œ HTTP μš”μ²­/응닡 검증
  • WebTestClient – WebFlux, μ‹€μ œ HTTP 호좜
  • RestAssured – REST API E2E ν…ŒμŠ€νŠΈ
    🧠 톡합 ν…ŒμŠ€νŠΈ
  • @SpringBootTest – 전체 μŠ€ν”„λ§ μ»¨ν…μŠ€νŠΈ
  • TestContainers – DB/Kafka/Redis 연동

🧱 λ‹¨μœ„ ν…ŒμŠ€νŠΈ

  • Repository / Service / Controller
  • JUnit / Kotest
  • Mock / Spy (Mockito)
  • @ParameterizedTest / @ValueSource

πŸ” 슬라이슀 ν…ŒμŠ€νŠΈ

  • @WebMvcTest – Controller만, Service/Repo Mock
  • @DataJpaTest – Repository/JPA
  • @RestClientTest – RestTemplate/WebClient
  • @JsonTest – JSON 직렬화/역직렬화
  • MockMvc – 컨트둀러 ν…ŒμŠ€νŠΈ

🎨 μ½”λ“œ 포맷 / μŠ€νƒ€μΌ

  • Spock – BDD μŠ€νƒ€μΌ

πŸ—‚ ν…ŒμŠ€νŠΈ 데이터

  • Fixture Monkey – μ½”λ“œ 기반 ν”½μŠ€μ²˜ 생성
  • DBRider – DB 기반 ν”½μŠ€μ²˜ 생성
profile
be_zion

2개의 λŒ“κΈ€

comment-user-thumbnail
2025λ…„ 11μ›” 10일

M1 ν™˜κ²½μ—μ„œ @SpringBootTest 속도에 λŒ€ν•œ νŠΈλŸ¬λΈ”μŠˆνŒ…μ— λŒ€ν•œ 기둝이 μΈμƒμ μ΄λ„€μš”!

λ‹΅κΈ€ 달기
comment-user-thumbnail
2025λ…„ 11μ›” 13일

이 글이 μ΄μ œλ΄€λ„€μš”. νŠΈλŸ¬λΈ” μŠˆνŒ… λ„ˆλ¬΄ μž¬λ°Œκ²Œμ½μ—ˆμŠ΅λ‹ˆλ‹€!

λ‹΅κΈ€ 달기