ν…ŒμŠ€νŠΈ 주도 개발 (TDD)

 πŸ’‘ TDDλ₯Ό ν™œμš©ν•œ 개발의 μž₯점을 μ•Œκ³ , λ¦¬νŒ©ν† λ§ν•˜λŠ” 과정을 ν•™μŠ΅ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

TDD

  • ν…ŒμŠ€νŠΈ 주도 개발둜 μ½”λ“œλ₯Ό μž‘μ„±ν•˜κΈ° 전에 ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € μž‘μ„±ν•˜λŠ” 개발 λ°©λ²•λ‘ μž…λ‹ˆλ‹€.
  • μ•„λž˜μ™€ 같은 μž‘μ—…μ„, 맀우 짧은 μ‚¬μ΄ν΄λ‘œ λ°˜λ³΅ν•œλ‹€λŠ” νŠΉμ§•μ΄ μžˆμŠ΅λ‹ˆλ‹€.
    • [λΉ¨κ°•] μ‹€νŒ¨ν•˜λŠ” μž‘μ€ ν…ŒμŠ€νŠΈ μž‘μ„±
    • [초둝] μ΅œλŒ€ν•œ λΉ λ₯Έ ν…ŒμŠ€νŠΈ 톡과
    • [νŒŒλž‘] λ¦¬νŒ©ν„°λ§ 과정을 ν†΅ν•΄μ„œ 쀑볡 & μ½”λ“œ κ°œμ„ 

  • TDD의 λͺ©μ μ€ μ½”λ“œκ°€ ν…ŒμŠ€νŠΈμ— μ§€μ •ν•œ μš”κ΅¬ 사항을 μΆ©μ‘±ν•˜λŠ”μ§€ ν™•μΈν•˜κ³ , μ½”λ“œλ₯Ό λ¦¬νŒ©ν† λ§ν•˜κΈ° μ‰½κ²Œ μˆ˜μ •ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

TDD 원칙

  • μ‹€νŒ¨ν•˜λŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  λ•ŒκΉŒμ§€ κ΅¬ν˜„ μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • μ»΄νŒŒμΌμ€ μ‹€νŒ¨ν•˜μ§€ μ•Šμ§€λ§Œ, 싀행이 μ‹€νŒ¨ν•˜λŠ” μ •λ„λ‘œλ§Œ μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.
  • ν˜„μž¬ μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈλ₯Ό 톡과할 μ •λ„λ‘œλ§Œ μ‹€μ œ μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

Kotlinμ—μ„œμ˜ TDD

  • Kotlin은 Javaλ₯Ό ν¬ν•¨ν•œ λ‹€λ₯Έ 언어에 λΉ„ν•΄ 맀우 κ°„κ²°ν•œ μ–Έμ–΄λ‘œ, 적은 μ½”λ“œλ₯Ό 톡해 κΈ°λŠ₯을 κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ½”λ“œ μˆ˜κ°€ μ μ„μˆ˜λ‘ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λŠ” μ‹œκ°„μ΄ κ°μ†Œν•˜λ―€λ‘œ Kotlin은 TDD에 μ ν•©ν•œ 언어라고도 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

TDD κ΅¬μ„±ν•˜κΈ°

  • BankAccount(은행 κ³„μ’Œ)λΌλŠ” 도메인 클래슀λ₯Ό κ΅¬μ„±ν•œλ‹€κ³  ν•  λ•Œ, TDDλ₯Ό κ΅¬ν˜„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

ν…ŒμŠ€νŠΈ μž‘μ„±

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class BankAccountTest {
    private lateinit var account: BankAccount

    @BeforeEach
    fun setUp() {
        account = BankAccount()
    }

    @Test
    fun testInitialBalance() {
        assertEquals(0, account.getBalance())
    }

    @Test
    fun testDeposit() {
        account.deposit(100)
        assertEquals(100, account.getBalance())
    }
}
  • μš°μ„  ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • accountμ—λŠ” μ•„λž˜μ™€ 같은 두가지 λ©”μ„œλ“œλ₯Ό 가지고 μžˆμŠ΅λ‹ˆλ‹€.
    • μž…κΈˆμ„ μ§„ν–‰ν•˜λŠ” deposit()
    • μž”μ•‘ 확인을 μ§„ν–‰ν•˜λŠ” getBalance()

도메인 클래슀 μž‘μ„±

class BankAccount {
    private var balance: Int = 0

    fun getBalance(): Int {
        return balance
    }

    fun deposit(amount: Int) {
        balance += amount
    }
}
  • 이 ν›„ μ›ν•˜λŠ” 도메인 클래슀λ₯Ό μƒμ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± μ‹œ κΈ°λŒ€ν•˜λŠ” κ°’μœΌλ‘œ λ°˜ν™˜ν•˜λ„λ‘ λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

λ¦¬νŒ©ν† λ§ 진행

  • 도메인 클래슀λ₯Ό μž‘μ„±ν•˜μ˜€λ‹€λ©΄, λ¦¬νŒ©ν† λ§μ„ 진행할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • κΈ°λŠ₯이 적은 ν΄λž˜μŠ€μ΄λ―€λ‘œ, λ³€μˆ˜ 이름 κ°œμ„  κ³Ό μ΄ˆκΈ°ν™” λ‘œμ§μ„ λΆ„λ¦¬ν•˜λŠ” κ°„λ‹¨ν•œ λ¦¬νŒ©ν† λ§μ„ μ§„ν–‰ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • λ˜ν•œ μ˜ˆμ™Έ 처리λ₯Ό μΆ”κ°€ν•˜μ—¬ μœ νš¨ν•˜μ§€ μ•Šμ€ μž…λ ₯을 λ°©μ§€ν•˜κ³ μž ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
class BankAccount {
    private var currentBalance: Int = 0

    init {
        initialize()
    }

    private fun initialize() {
        currentBalance = 0
    }

    fun getBalance(): Int = currentBalance

    fun deposit(amount: Int) {
		    require(amount > 0)
        currentBalance += amount
    }
}

λ°˜λ³΅ν•˜κΈ°

  • ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± → 도메인 둜직 κ΅¬ν˜„ → λ¦¬νŒ©ν† λ§ 단계λ₯Ό κ±°μ³€μœΌλ―€λ‘œ, λ‹€μ‹œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μˆ˜μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • deposit() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ μœ νš¨μ„±μ„ κ²€μ‚¬ν•˜λ―€λ‘œ, μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ—¬ μœ νš¨μ„± 검증이 μ œλŒ€λ‘œ μ΄λ£¨μ–΄μ§€λŠ”μ§€ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
@Test
fun testDepositNegativeAmount() {
		val exception = assertThrows<IllegalArgumentException> {
				account.deposit(-100)
    }
    assertEquals("Deposit amount must be positive", exception.message)
}
  • 이와 같이 λ¦¬νŒ©ν† λ§ 과정을 톡해 μ μ§„μ μœΌλ‘œ κΈ°λŠ₯을 κ°œμ„ ν•˜λ©΄μ„œ 도메인을 TDD둜 κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

κ²°λ‘ 

  • TDDλ₯Ό ν™œμš©ν•œλ‹€λ©΄ 쒋은 섀계λ₯Ό ν•  수 μžˆλŠ” 밑바탕이 되며, CI/CD에 μ μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈλ₯Ό μžλ™ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ‹€μ–‘ν•œ μΌ€μ΄μŠ€λ₯Ό λ¨Όμ € μƒκ°ν•˜κ³  κ°œλ°œν•˜λ©΄ μ½”λ“œ μž‘μ„± μ‹œ κ°„κ²°ν•˜κ³  쒋은 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μΆ•μ λœ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” 높아진 ν…ŒμŠ€νŠΈ μ»€λ²„λ¦¬μ§€λ‘œ 버그 μˆ˜μ • 및 λ¦¬νŒ©ν† λ§μ΄ μˆ˜μ›” ν•΄, μœ μ§€λ³΄μˆ˜κ°€ λ†’μ•„μ§„λ‹€λŠ” 큰 μž₯점을 가지고 μžˆμŠ΅λ‹ˆλ‹€.
  • λ°˜λ©΄μ— ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜κΈ° μœ„ν•œ λŸ¬λ‹μ»€λΈŒκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€.
    • Junit, Espresso λ“±μ˜ APIλ₯Ό ν•™μŠ΅ν•΄μ•Ό 함
  • 기획 변경에 λ”°λ₯Έ μ½”λ“œκ°€ μˆ˜μ •λœλ‹€λ©΄, 지속적인 ν…ŒμŠ€νŠΈ μ½”λ“œ μˆ˜μ • μ—­μ‹œ λΆˆκ°€ν”Όν•  수 μ—†μŠ΅λ‹ˆλ‹€.