λ…Έλ¨ΈμŠ€ λͺ¨λ°”μΌνŒ€μ΄ TDDλ₯Ό ν™œμš©ν•˜λŠ” 법

daesoon
  • #TDD
  • #BDD
  • #Android
  • #iOS

λ“€μ–΄κ°€λ©°

μ•ˆλ…•ν•˜μ„Έμš”! μ €λŠ” λ…Έλ¨ΈμŠ€ λͺ¨λ°”μΌνŒ€μ˜ 개발 λ¦¬λ“œ μ΅œλŒ€μˆœμž…λ‹ˆλ‹€. ν˜„μž¬λŠ” frommμ΄λΌλŠ” μ„œλΉ„μŠ€λ₯Ό κ°œλ°œν•˜λ©°, λ‹€μ–‘ν•œ 기술적 λ„μ „κ³Όμ œλ₯Ό ν•΄κ²°ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

개발자라면 λˆ„κ΅¬λ‚˜ ν•œ λ²ˆμ―€μ€ TDD(Test-Driven Development)λΌλŠ” μš©μ–΄λ₯Ό μ ‘ν•˜κ±°λ‚˜, 이 방법둠을 λ„μž…ν•˜λ € μ‹œλ„ν•΄λ³Έ κ²½ν—˜μ΄ μžˆμ„ κ²ƒμž…λ‹ˆλ‹€. TDDλŠ” λ§Žμ€ 개발 μ„œμ κ³Ό κ°•μ—°μ—μ„œ 높은 μ½”λ“œ ν’ˆμ§ˆ, μ•ˆμ •μ„±, μœ μ§€λ³΄μˆ˜μ„±μ„ 보μž₯ν•˜λŠ” ν˜μ‹ μ μΈ λ°©λ²•λ‘ μœΌλ‘œ μ†Œκ°œλ©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이λ₯Ό μ‹€λ¬΄μ—μ„œ μ‹€μ œλ‘œ μ μš©ν•˜κ³ , 특히 운영 쀑인 μ„œλΉ„μŠ€μ˜ 개발 κ³Όμ •μ—μ„œ μ²΄κ³„μ μœΌλ‘œ ν™œμš©ν•œ κ²½ν—˜μ„ 가진 κ°œλ°œμžλŠ” λ§Žμ§€ μ•Šμ„ κ²ƒμž…λ‹ˆλ‹€.

특히, fromm처럼 λ‹€μ–‘ν•œ κΈ°λŠ₯을 ν¬ν•¨ν•˜κ³  있으며, λ³΅μž‘ν•œ UI/UX μš”κ΅¬μ‚¬ν•­κ³Ό μŠ€νƒ€νŠΈμ—… 특유의 λΉ λ₯Έ 개발 사이클을 μΆ©μ‘±ν•΄μ•Ό ν•˜λŠ” λͺ¨λ°”일 μ•± μ„œλΉ„μŠ€μ˜ 경우 TDDλ₯Ό μ μš©ν•˜λŠ” 과정은 λ”μš± κΉŒλ‹€λ‘­μŠ΅λ‹ˆλ‹€. μ΄λ‘ μ μœΌλ‘œλŠ” μ΄μƒμ μ΄μ§€λ§Œ, ν˜„μ‹€μ μœΌλ‘œλŠ” μ œν•œλœ λ¦¬μ†ŒμŠ€μ™€ λΉ λ₯Έ μ˜μ‚¬κ²°μ • μ†μ—μ„œ TDDλ₯Ό λ„μž…ν•˜κ³  μœ μ§€ν•˜κΈ°λž€ 쉽지 μ•ŠμŠ΅λ‹ˆλ‹€.

κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³ , λ…Έλ¨ΈμŠ€ λͺ¨λ°”μΌνŒ€μ€ TDDλ₯Ό 톡해 μ½”λ“œ ν’ˆμ§ˆκ³Ό 개발 생산성을 높이기 μœ„ν•΄ κΎΈμ€€νžˆ λ…Έλ ₯ν•΄μ™”μŠ΅λ‹ˆλ‹€. 이 κΈ€μ—μ„œλŠ” 저희 νŒ€μ΄ μ‹€λ¬΄μ—μ„œ TDDλ₯Ό μ–΄λ–»κ²Œ μ μš©ν–ˆλŠ”μ§€, κ²ͺ은 어렀움과 이λ₯Ό κ·Ήλ³΅ν•˜κΈ° μœ„ν•œ κ³Όμ •μ—μ„œμ˜ μ‹œν–‰μ°©μ˜€λ₯Ό κ³΅μœ ν•˜κ³ μž ν•©λ‹ˆλ‹€. 이 글이 μ—¬λŸ¬λΆ„μ—κ²Œ μ‹€μ§ˆμ μΈ 도움과 μ˜κ°μ„ 쀄 수 있기λ₯Ό λ°”λžλ‹ˆλ‹€.

TDDλž€?

TDD(Test-Driven Development)λŠ” μΌ„νŠΈ 벑(Kent Beck)이 κ³ μ•ˆν•œ μ†Œν”„νŠΈμ›¨μ–΄ 개발 λ°©λ²•λ‘ μœΌλ‘œ, β€œν…ŒμŠ€νŠΈ 주도 κ°œλ°œβ€μ΄λΌλŠ” 이름 κ·ΈλŒ€λ‘œ ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € μž‘μ„±ν•˜κ³  이λ₯Ό 기반으둜 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. TDDλŠ” λ‹€μŒκ³Ό 같은 반볡적 과정을 톡해 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

  • Red: μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈ μž‘μ„±
    • λ¨Όμ € μž‘μ„±ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ μ‹€νŒ¨ν•˜λ„λ‘ μ„€κ³„ν•©λ‹ˆλ‹€. μ΄λŠ” κ΅¬ν˜„λ˜μ§€ μ•Šμ€ κΈ°λŠ₯을 λͺ…ν™•νžˆ μ •μ˜ν•˜λŠ” λ‹¨κ³„μž…λ‹ˆλ‹€.
  • Green: ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όν•˜λŠ” μ΅œμ†Œν•œμ˜ μ½”λ“œ μž‘μ„±
    • ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν•˜λ„λ‘ κ°„λ‹¨ν•˜κ²Œ μ½”λ“œλ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€. 이 λ‹¨κ³„μ—μ„œλŠ” μ΅œμ†Œν•œμ˜ κΈ°λŠ₯ κ΅¬ν˜„μ— μ§‘μ€‘ν•©λ‹ˆλ‹€.
  • Refactor: μ½”λ“œ κ°œμ„ 
    • ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•œ ν›„, μ½”λ“œλ₯Ό λ¦¬νŒ©ν„°λ§ν•˜μ—¬ 가독성과 μœ μ§€λ³΄μˆ˜μ„±μ„ κ°œμ„ ν•©λ‹ˆλ‹€. μ΄λ•Œ ν…ŒμŠ€νŠΈκ°€ λ‹€μ‹œ μ‹€νŒ¨ν•˜μ§€ μ•Šλ„λ‘ μ£Όμ˜ν•©λ‹ˆλ‹€.

TDD의 κΈ°λŒ€ 효과

TDD의 μ£Όμš” μž₯점은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • 버그 κ°μ†Œ: ν…ŒμŠ€νŠΈλ₯Ό 기반으둜 μ½”λ“œλ₯Ό μž‘μ„±ν•˜κΈ° λ•Œλ¬Έμ— μ½”λ“œμ˜ μ•ˆμ •μ„±μ΄ λ†’μ•„μ§‘λ‹ˆλ‹€.
  • λ¦¬νŒ©ν„°λ§ μ•ˆμ „μ„±: μ½”λ“œ λ³€κ²½ μ‹œ ν…ŒμŠ€νŠΈκ°€ μ˜¬λ°”λ₯΄κ²Œ λ™μž‘ν•˜λŠ”μ§€ 보μž₯ν•©λ‹ˆλ‹€.
  • λͺ…ν™•ν•œ 섀계: ν…ŒμŠ€νŠΈ μž‘μ„± κ³Όμ •μ—μ„œ μΈν„°νŽ˜μ΄μŠ€μ™€ 둜직이 μžμ—°μŠ€λŸ½κ²Œ μ„€κ³„λ©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ TDDλŠ” λ‹¨μˆœν•œ μ½”λ”© 기법을 λ„˜μ–΄, μ†Œν”„νŠΈμ›¨μ–΄ 개발의 μ² ν•™μ΄μž μ‚¬κ³ λ°©μ‹μœΌλ‘œ μ΄ν•΄λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. μ‹€μ œλ‘œ 이λ₯Ό 싀무에 μ„±κ³΅μ μœΌλ‘œ μ μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ„œλΉ„μŠ€ νŠΉμ„±κ³Ό νŒ€μ˜ 상황에 λ§žλŠ” μ „λž΅μ  접근이 ν•„μš”ν•©λ‹ˆλ‹€.

λͺ¨λ°”μΌνŒ€μ—μ„œμ˜ TDD 적용

TDD vs BDD: μ‹€λ¬΄μ—μ„œμ˜ κ³ λ―Ό

λͺ¨λ°”일 μ•± 개발 ν™˜κ²½μ—μ„œλŠ” TDD의 전톡적인 λ°©μ‹λ§ŒμœΌλ‘œλŠ” ν•΄κ²°ν•  수 μ—†λŠ” λ¬Έμ œλ“€μ΄ 자주 λ°œμƒν•©λ‹ˆλ‹€. 이λ₯Ό κ·Ήλ³΅ν•˜κΈ° μœ„ν•΄ 저희 νŒ€μ€ TDD와 ν•¨κ»˜ BDD(Behavior-Driven Development) μŠ€νƒ€μΌμ„ 적절히 κ²°ν•©ν•˜μ—¬ ν™œμš©ν–ˆμŠ΅λ‹ˆλ‹€.

전톡적 TDD의 ν•œκ³„

μΌ„νŠΈ 벑의 전톡적인 TDD 방식은 μž‘μ€ λ‹¨μœ„μ˜ ν…ŒμŠ€νŠΈλ₯Ό 기반으둜 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 데 쀑점을 λ‘‘λ‹ˆλ‹€. ν•˜μ§€λ§Œ λͺ¨λ°”일 μ•± κ°œλ°œμ—μ„œλŠ” λ‹€μŒκ³Ό 같은 ν•œκ³„μ μ΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

  • ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ˜ 폭발적 증가 : UI/UX와 κ΄€λ ¨λœ λ‹€μ–‘ν•œ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό λͺ¨λ‘ ν…ŒμŠ€νŠΈν•˜λ €λ©΄ μΌ€μ΄μŠ€κ°€ κ³Όλ„ν•˜κ²Œ λ§Žμ•„μ§‘λ‹ˆλ‹€.
  • 변화에 λ”°λ₯Έ μœ μ§€λ³΄μˆ˜ λΆ€λ‹΄ : λͺ¨λ°”일 앱은 UI 변경이 λΉˆλ²ˆν•˜λ©°, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§λ„ 자주 μˆ˜μ •λ©λ‹ˆλ‹€. λͺ¨λ“  λ³€ν™”λ§ˆλ‹€ ν…ŒμŠ€νŠΈλ₯Ό μˆ˜μ •ν•˜λŠ” 것은 ν˜„μ‹€μ μœΌλ‘œ μ–΄λ ΅μŠ΅λ‹ˆλ‹€.
  • 도메인 μ΄ν•΄μ˜ 어렀움 : μ§€λ‚˜μΉ˜κ²Œ μ„ΈλΆ„ν™”λœ ν…ŒμŠ€νŠΈλŠ” 였히렀 도메인 λ‘œμ§μ„ νŒŒμ•…ν•˜κΈ° μ–΄λ ΅κ²Œ λ§Œλ“­λ‹ˆλ‹€.

BDD둜의 μ „ν™˜: μ‚¬μš©μž 쀑심 ν…ŒμŠ€νŠΈ

μ΄λŸ¬ν•œ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄, 저희 νŒ€μ€ Given-When-Then νŒ¨ν„΄μ„ 기반으둜 ν•œ BDD 방식을 λ³‘ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, β€œν”„λ‘œν•„μ„ ν΄λ¦­ν–ˆμ„ λ•Œ ν•΄λ‹Ή ν”„λ‘œν•„μ΄ μ‘΄μž¬ν•˜λ©΄ ν”„λ‘œν•„ 이미지 ν™”λ©΄μœΌλ‘œ μ΄λ™ν•œλ‹€.β€λΌλŠ” μ‚¬μš©μž μ‹œλ‚˜λ¦¬μ˜€λ₯Ό 기반으둜 μž‘μ„±ν•œ ν…ŒμŠ€νŠΈλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

fun `OnClickProfile_Profile exist_navigate ToProfileImage`() = runTest {
  //test code
}

BDD μŠ€νƒ€μΌμ„ 톡해 μž‘μ„±λœ ν…ŒμŠ€νŠΈλŠ” λ‹€μŒκ³Ό 같은 μž₯점을 κ°€μ§‘λ‹ˆλ‹€.

  • λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­μ„ λͺ…ν™•νžˆ 반영 : ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό 톡해 μ‹€μ œ μš”κ΅¬μ‚¬ν•­μ΄ μ½”λ“œμ— λͺ…ν™•νžˆ λ…Ήμ•„λ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 변화에 μœ μ—°ν•œ 섀계 κ°€λŠ₯ : λΉˆλ²ˆν•œ 변화에도 도메인 λ‘œμ§μ„ μ€‘μ‹¬μœΌλ‘œ μ•ˆμ •μ μΈ ν…ŒμŠ€νŠΈλ₯Ό μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν•„μš”μ— 따라, 도메인 λ‚΄λΆ€ λ‘œμ§μ΄λ‚˜ νŠΉμ • μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ 검증을 μœ„ν•΄ 전톡적인 TDD 방식도 적절히 λ³‘ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

TDD의 λͺ©μ μ€ μ„€κ³„μ˜ ν’ˆμ§ˆ ν–₯상

TDDλŠ” λ‹¨μˆœνžˆ 버그λ₯Ό λ°©μ§€ν•˜λŠ” 도ꡬ, 엣지 μΌ€μ΄μŠ€λ₯Ό μ κ²€ν•˜κΈ° μœ„ν•œ 도ꡬ가 μ•„λ‹ˆλΌ, 섀계 ν’ˆμ§ˆμ„ λ†’μ΄λŠ” μ² ν•™μœΌλ‘œ 이해해야 κ·Έ κ°€μΉ˜λ₯Ό μ œλŒ€λ‘œ ν™œμš© ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 저희 νŒ€μ€ 섀계 ν’ˆμ§ˆμ˜ ν–₯상을 μ£Ό λͺ©μ μœΌλ‘œ ν•˜μ—¬ TDDλ₯Ό ν™œμš©ν•˜μ˜€μœΌλ©° λ‹€μŒκ³Ό 같은 섀계 κ°œμ„  효과λ₯Ό κ²½ν—˜ν–ˆμŠ΅λ‹ˆλ‹€.

  • μΈν„°νŽ˜μ΄μŠ€ 섀계 : ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € μž‘μ„±ν•˜λŠ” κ³Όμ •μ—μ„œ μžμ—°μŠ€λŸ½κ²Œ μΈν„°νŽ˜μ΄μŠ€κ°€ κ΅¬μ‘°ν™”λ©λ‹ˆλ‹€.
  • 사전 리뷰 κ°€λŠ₯ : ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μž‘μ„±ν•˜λ©΄μ„œ 섀계 λ‹¨κ³„μ—μ„œλΆ€ν„° 리뷰λ₯Ό 받을 수 μžˆμŠ΅λ‹ˆλ‹€.
  • ν’ˆμ§ˆκ³Ό 생산성 ν–₯상 : 잘 μ„€κ³„λœ μΈν„°νŽ˜μ΄μŠ€λŠ” μ½”λ“œ ν’ˆμ§ˆμ„ 높이고, λΆˆν•„μš”ν•œ μˆ˜μ • λΉ„μš©μ„ μ€„μž…λ‹ˆλ‹€.

TDD에 μ§‘μ°©ν•˜μ§€ μ•ŠλŠ” μœ μ—°ν•¨

저희 νŒ€μ€ TDDλ₯Ό μ—„κ²©νžˆ μ μš©ν•˜λŠ” λŒ€μ‹ , 효율적인 섀계와 λΉ λ₯Έ 개발 μ£ΌκΈ° μœ μ§€λ₯Ό λͺ©ν‘œλ‘œ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ“  μ˜μ—­μ— TDDλ₯Ό μ μš©ν•˜κΈ°λ³΄λ‹€λŠ”, 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— μ§‘μ€‘ν•˜μ—¬ 효과λ₯Ό κ·ΉλŒ€ν™”ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

μ €ν¬λŠ” β€œμ™„λ²½ν•œ ν…ŒμŠ€νŠΈ 컀버리지”가 λͺ©ν‘œκ°€ μ•„λ‹ˆλΌ, 효율적인 섀계와 λΉ λ₯Έ 개발 μ£ΌκΈ°λ₯Ό μœ μ§€ν•˜λŠ” 것을 λͺ©ν‘œλ‘œ μ‚Όκ³  μžˆμŠ΅λ‹ˆλ‹€. μ„œλΉ„μŠ€λŠ” 자주 λ³€κ²½λ˜κΈ° λ•Œλ¬Έμ—, λͺ¨λ“  ν…ŒμŠ€νŠΈλ₯Ό μˆ˜μ •ν•˜κ³  μœ μ§€λ³΄μˆ˜ν•˜λŠ” 것은 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€. TDD에 κ³Όλ„ν•˜κ²Œ 집착할 경우, 였히렀 속도가 λŠλ €μ§€κ³  ν…ŒμŠ€νŠΈ μ½”λ“œ μžμ²΄κ°€ μœ μ§€λ³΄μˆ˜μ˜ λŒ€μƒμ΄ 될 μœ„ν—˜μ΄ μžˆμŠ΅λ‹ˆλ‹€.

μ €ν¬λŠ” TDDλ₯Ό λͺ¨λ“  λ²”μœ„μ— μ μš©ν•˜μ§€ μ•Šκ³ , 핡심 μ˜μ—­μ—λ§Œ μ œν•œμ μœΌλ‘œ λ„μž…ν–ˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, Clean Architectureλ₯Ό 기반으둜 ν•œ μ„œλΉ„μŠ€ μ„€κ³„μ—μ„œ, Usecase λ ˆμ΄μ–΄μ—λ§Œ TDDλ₯Ό μ§‘μ€‘μ μœΌλ‘œ μ μš©ν•˜κ³ , Presentation 및 Data λ ˆμ΄μ–΄μ—μ„œλŠ” ν•„μš”μ— 따라 μ œν•œμ μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 TDD의 이점과 λΉ λ₯Έ 개발 μ£ΌκΈ° μ‚¬μ΄μ˜ κ· ν˜•μ„ μœ μ§€ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ°”μΌνŒ€μ—μ„œμ˜ TDD μ˜ˆμ‹œ 사둀

κ°„λ‹¨ν•œ μ˜ˆμ‹œλ₯Ό ν†΅ν•΄μ„œ λͺ¨λ°”μΌνŒ€μ—μ„œ TDDλ₯Ό μ–΄λ–»κ²Œ μ μš©ν•˜κ³  μžˆλŠ”μ§€ μ†Œκ°œν•˜κ² μŠ΅λ‹ˆλ‹€. λ¨Όμ € λ‹€μŒκ³Ό 같은 μš”κ΅¬μ‚¬ν•­μ„ κ΅¬ν˜„ν•΄μ•Όν•œλ‹€κ³  κ°€μ •ν•˜κ² μŠ΅λ‹ˆλ‹€.

ν”„λ‘œν•„ ν™”λ©΄μ§„μž… μ‹œ ν”„λ‘œν•„ 데이터λ₯Ό λΆˆλŸ¬μ™€μ„œ μ‚¬μš©μžμ—κ²Œ λ…ΈμΆœν•œλ‹€.

ν˜„μ‹€μ—μ„œλŠ” μœ„μ™€ 같은 λͺ…ν™•ν•œ μš”κ΅¬μ‚¬ν•­λ³΄λ‹€ 보닀 λ³΅μž‘ν•˜κ³ , ν™”λ©΄κΈ°νš, μ •μ±… 등이 ν¬ν•¨λœ κΈ°νšμ„œκ°€ 전달 될 κ²ƒμž…λ‹ˆλ‹€. 일단 μ˜ˆμ‹œ μ‚¬λ‘€μ—μ„œλŠ” λ‹¨μˆœνžˆ ν”„λ‘œν•„ ν™”λ©΄ μ§„μž… μ‹œ μ‚¬μš©μžμ˜ ν”„λ‘œν•„μ„ λ…ΈμΆœν•˜λŠ” κΈ°λŠ₯을 κ΅¬ν˜„ν•œλ‹€κ³  κ°€μ •ν•˜κ² μŠ΅λ‹ˆλ‹€. μš”κ΅¬μ‚¬ν•­μ„ 전달 λ°›μœΌλ©΄ λ‹΄λ‹ΉμžλŠ” Usecaseλ₯Ό μ„€κ³„ν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λ•Œ Usecase 섀계가 TDD와 ν•¨κ»˜ μ§„ν–‰λ©λ‹ˆλ‹€.

μž‘μ—…μžλŠ” ν”„λ‘œν•„ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” FetchProfileUsecase λ₯Ό λ§Œλ“€κ³ μž ν•˜κ³  μ•„λž˜μ™€ 같이 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό 생각해 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

@Test
fun `launch profile screen_given profile id_return profile data`() = runTest {
    
}

ν•΄λ‹Ή ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” ν”„λ‘œν•„ 화면에 μ§„μž…ν–ˆμ„ λ•Œ, 주어진 ν”„λ‘œν•„IDλ₯Ό 가지고 profile data λ₯Ό λ¦¬ν„΄ν•΄μ•Όν•˜λŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ λ©λ‹ˆλ‹€. λ¬Όλ‘  이외에도 λ‹€μ–‘ν•œ 기획적인 μ˜ˆμ™Έμƒν™© (ex. ν”„λ‘œν•„ 아이디가 없을 λ•ŒλŠ” 였λ₯˜ νŒμ—…μ„ λ…ΈμΆœν•œλ‹€ λ“±)을 λ°˜μ˜ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ„ μžˆκ² μ§€λ§Œ ν•΄λ‹Ή 뢀뢄듀은 μƒλž΅ν•˜κ² μŠ΅λ‹ˆλ‹€. 이제 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Όν•˜λŠ”λ° μ΄λ•Œ Repository Interface 섀계가 ν•¨κ»˜ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€. μž‘μ—…μžλŠ” μ„œλ²„μ—μ„œ ν”„λ‘œν•„ 데이터λ₯Ό 가져와야 ν•˜κΈ° λ•Œλ¬Έμ— repository interface λ₯Ό κ΅¬μ„±ν•˜κ³  λ‹€μŒκ³Ό 같이 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„± ν•  κ²ƒμž…λ‹ˆλ‹€. λŒ€λž΅μ μœΌλ‘œλŠ” μ•„λž˜μ™€ 같은 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„± ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (μ•„λž˜ μ½”λ“œλŠ” κ°„λž΅ν•˜κ²Œ μž‘μ„± 된 μ˜ˆμ‹œμž…λ‹ˆλ‹€.)

@Test
fun `launch profile screen_given profile id_return profile data`() = runTest {
    val mockRepository = mockk<ProfileRepository>()
    val profile = Profile("User123", "user123@example.com")
    coEvery { mockRepository.getProfile(any()) } returns profile

    val useCase = FetchProfileUseCase(mockRepository)
    val result = useCase("user123")

    assertEquals(profile, result)
}

이 과정을 톡해 Repository μΈν„°νŽ˜μ΄μŠ€μ™€ Profile μ΄λΌλŠ” 도메인이 섀계가 λ©λ‹ˆλ‹€. ν•΄λ‹Ή μ½”λ“œμ™€ μΈν„°νŽ˜μ΄μŠ€, 도메인 λͺ¨λΈμ€ μ½”λ“œλ¦¬λ·°λ₯Ό ν†΅ν•΄μ„œ ν•œλ²ˆ 더 κ²€μ¦λ˜κ³  이후 Dataλ ˆμ΄μ–΄, Presentationλ ˆμ΄μ–΄ μž‘μ—…μ΄ μ§„ν–‰λ©λ‹ˆλ‹€.

μœ„ μ˜ˆμ‹œμ—μ„œλŠ” 맀우 κ°„λž΅ν•œ μΌ€μ΄μŠ€λ‘œ μΆ•μ•½ν•΄μ„œ μ„€λͺ…을 λ“œλ ΈλŠ”λ°μš”, μ‹€μ œ λ³΅μž‘ν•œ 둜직일 수둝 TDD의 효과λ₯Ό 많이 λŠλ‚„ 수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œ 저희가 μ‚¬μš©ν•˜κ³  μžˆλŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ˜ μ‹€ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ExampleUseCaseTest {

    private lateinit var useCase: ExampleUseCase

    @MockK
    private lateinit var repositoryA: RepositoryA

    @MockK
    private lateinit var repositoryB: RepositoryB

    @MockK
    private lateinit var handler: Handler

    private val fixture = kotlinFixture()

    @BeforeAll
    fun setup() {
        MockKAnnotations.init(this)
        useCase = ExampleUseCase(
            repositoryA = repositoryA,
            repositoryB = repositoryB,
            handler = handler
        )
    }

    @Test
    fun `Given repositoryB throws NotFoundException When useCase is invoked Then result is Failure`() = runTest {
        val testData: DataType = fixture()

        coEvery { repositoryA.getData(any()) } returns testData
        coEvery { repositoryB.connect(any(), any()) } throws CustomException.NotFoundException
        coEvery { handler.cleanup() } returns Unit

        val result = useCase.invoke(ExampleUseCase.Param(id = fixture(), key = fixture()))

        assertThat(result).isEqualTo(ExampleUseCase.Result.Failure)
    }

    @Test
    fun `Given repositoryA throws DataNotFound When useCase is invoked Then result is Failure`() = runTest {
        coEvery { repositoryA.getData(any()) } throws CustomException.DataNotFound
        coEvery { repositoryB.connect(any(), any()) } returns Unit
        coEvery { handler.cleanup() } returns Unit

        val result = useCase.invoke(ExampleUseCase.Param(id = fixture(), key = fixture()))

        assertThat(result).isEqualTo(ExampleUseCase.Result.Failure)
    }

    @Test
    fun `Given valid inputs When all operations succeed Then result is Success`() = runTest {
        val data: DataType = fixture()
        val metadata: MetadataType = fixture()
        val additionalInfo: AdditionalInfo = fixture()

        coEvery { repositoryA.getData(any()) } returns data
        coEvery { repositoryB.connect(any(), any()) } returns Unit
        coEvery { repositoryB.getMetadata() } returns metadata
        coEvery { repositoryB.getAdditionalInfo() } returns additionalInfo
        every { repositoryB.isConditionMet() } returns true

        val result = useCase.invoke(ExampleUseCase.Param(id = fixture(), key = fixture()))

        assertThat(result).isEqualTo(
            ExampleUseCase.Result.Success(
                data = data,
                metadata = metadata,
                additionalInfo = additionalInfo,
                conditionMet = true
            )
        )
    }

    @Test
    fun `Given metadata indicates termination When useCase is invoked Then result is Failure`() = runTest {
        val data: DataType = fixture()

        coEvery { repositoryA.getData(any()) } returns data
        coEvery { repositoryB.connect(any(), any()) } returns Unit
        coEvery { repositoryB.getMetadata() } returns MetadataType.TERMINATED

        val result = useCase.invoke(ExampleUseCase.Param(id = fixture(), key = fixture()))

        assertThat(result).isEqualTo(ExampleUseCase.Result.Failure)
    }
}

마치며

TDDλŠ” λͺ¨λ“  ν™˜κ²½μ—μ„œ λ™μΌν•˜κ²Œ 적용될 수 μ—†μŠ΅λ‹ˆλ‹€. νŒ€μ˜ μ—­λŸ‰, μ„œλΉ„μŠ€μ˜ νŠΉμ„±, νšŒμ‚¬μ˜ 상황에 따라 κ·Έ 방식은 달라져야 ν•©λ‹ˆλ‹€. 저희 λ…Έλ¨ΈμŠ€ λͺ¨λ°”μΌνŒ€μ€ TDD와 BDDλ₯Ό 적절히 κ²°ν•©ν•˜μ—¬ 핡심적인 μ˜μ—­μ— μ§‘μ€‘ν•¨μœΌλ‘œμ¨, 효율적인 섀계와 μ½”λ“œ ν’ˆμ§ˆμ„ λ™μ‹œμ— λ‹¬μ„±ν•˜κ³ μž λ…Έλ ₯ν•΄μ™”μŠ΅λ‹ˆλ‹€.

이 글이 TDD λ„μž…μ„ κ³ λ―Όν•˜κ³  계신 λΆ„λ“€μ—κ²Œ μ‘°κΈˆμ΄λ‚˜λ§ˆ 도움이 되길 바라며, 각자의 ν™˜κ²½μ— λ§žλŠ” 졜적의 방법을 μ°Ύμ•„κ°€μ‹œκΈΈ μ‘μ›ν•©λ‹ˆλ‹€. κ°μ‚¬ν•©λ‹ˆλ‹€! 😊

← λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ°

Art Changes Life

λ…Έλ¨ΈμŠ€μ™€ ν•¨κ»˜ μ—”ν„°ν…Œν¬ 산업을 ν˜μ‹ ν•΄λ‚˜κ°ˆ 멀버λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.

μ±„μš© 쀑인 곡고 보기