BACKEND

TDD, 클린 μ½”λ“œ with Kotlin 6κΈ° λΈ”λž™μž­ ν”Όλ“œλ°±

솑이 🫧 2023. 7. 24. 00:45

Step2

https://github.com/next-step/kotlin-blackjack/pull/528

Test Double

λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ μ˜μ‘΄μ„± μ£Όμž… 뢀뢄에 ν…ŒμŠ€νŠΈ 더블을 μ‚¬μš©ν•΄ ν…ŒμŠ€νŠΈλ₯Ό μ’€ 더 νŽΈλ¦¬ν•˜κ²Œ ν•  수 μžˆλ„λ‘ κ΅¬ν˜„ν•˜μ˜€λ‹€. λ‹€λ§Œ, ν”Όλ“œλ°±μ„ λ°›μœΌλ©° Stub, Mock, Fake λ“± ν…ŒμŠ€νŠΈ λ”λΈ”μ˜ μ’…λ₯˜μ— λŒ€ν•΄ λͺ…ν™•νžˆ μ•Œκ³  μ‚¬μš©ν•˜λŠ”κ²Œ 쒋을 것 κ°™μ•„ 정리해보렀 ν•œλ‹€.

  • Dummy
    • λ™μž‘ν•˜μ§€ μ•Šμ•„λ„ ν…ŒμŠ€νŠΈμ—λŠ” 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠλŠ” 객체λ₯Ό Dummy 객체라고 ν•œλ‹€.
    • λ‹¨μˆœ μΈμŠ€ν„΄μŠ€ν™”ν•œ 객체λ₯Ό μ˜λ―Έν•œλ‹€.
  • Stub
    • ν…ŒμŠ€νŠΈμ—μ„œ 호좜된 μš”μ²­μ— λŒ€ν•΄ 미리 μ€€λΉ„ν•΄λ‘” κ²°κ³Όλ₯Ό μ œκ³΅ν•œλ‹€.
    • 즉, ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ 미리 λ§Œλ“€μ–΄λ†“μ€ κ°μ²΄λ§Œμ„ λ°˜ν™˜ν•œλ‹€. (객체의 μƒνƒœλ₯Ό 검증할 λ•Œ 주둜 μ‚¬μš©ν•œλ‹€.)
  • Mock
    • ν˜ΈμΆœμ— λŒ€ν•œ κΈ°λŒ€λ₯Ό λͺ…μ„Έν•˜κ³  λ‚΄μš©μ— 따라 λ™μž‘ν•˜λ„λ‘ ν”„λ‘œκ·Έλž˜λ° 된 객체이닀.
    • μ‹€μ œλ‘œ ν•¨μˆ˜κ°€ 잘 ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€ κ²€μ¦ν•˜λŠ”λ° 더 λͺ©μ μ΄ 있고(행동 검증) ν…ŒμŠ€νŠΈ 속도 κ°œμ„  및 κ²€μ¦ν•˜κ³  싢은 ν…ŒμŠ€νŠΈμ— μ§‘μ€‘ν•˜κ³  싢을 λ•Œ κ²€μ¦ν•˜κ³ μž ν•˜λŠ” 것을 μ œμ™Έν•œ λ‹€λ₯Έ 객체듀을 λͺ¨ν‚Ήν•˜μ—¬ μ‚¬μš©ν•œλ‹€.
  • Fake
    • λ³΅μž‘ν•œ λ‘œμ§μ„ 가진 ν•¨μˆ˜μ˜ λ™μž‘μ„ λ‹¨μˆœν™”ν•˜μ—¬ κ΅¬ν˜„ν•œ 객체이닀.
    • 즉, λ™μž‘μ€ ν•˜μ§€λ§Œ μ‹€μ œ μ‚¬μš©λ˜λŠ” 객체처럼 μ •κ΅ν•˜κ²Œ λ™μž‘ν•˜μ§€λŠ” μ•ŠλŠ” 객체λ₯Ό λ§ν•œλ‹€.
    • 예λ₯Ό λ“€λ©΄, InMemoryRepository와 같이 ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ κ°€μ§œ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό λ§Œλ“œλŠ” 것과 같은 μ˜ˆμ‹œκ°€ μžˆλ‹€. μ •κ΅ν•˜μ§€λŠ” μ•Šμ§€λ§Œ μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€μ™€ λ™μΌν•œ 역할을 ν•œλ‹€.

μ°Έκ³ 

https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/

Lazy Property μ‚¬μš©

λΆˆλ³€ 객체라면 μ ‘κ·Όν•  λ•Œλ§ˆλ‹€ λ‘œμ§μ„ μ‹€ν–‰ν•˜μ§€ μ•Šκ³  처음 μ ‘κ·Όν–ˆμ„ λ•Œ ν•œλ²ˆλ§Œ μ‹€ν–‰ν•˜κ³  μ΄ˆκΈ°ν™”ν•˜λŠ” 방법을 μ‚¬μš©ν•  수 μžˆλ‹€.

μ΄λ ‡κ²Œ μ‚¬μš©ν•  경우 μ€‘λ³΅λ˜λŠ” 계산을 ν”Όν•΄ λ©”λͺ¨λ¦¬ μ ˆμ•½μ„ ν•  수 μžˆλ‹€.

val lazyValue: String by lazy {
    println("computed!") // 처음 μ ‘κ·Όν•  λ•Œ λ”± ν•œλ²ˆλ§Œ μ‹€ν–‰
    "Hello"
}

//  println(lazyValue)
//  println(lazyValue)
//  -- 좜λ ₯ -- 
computed!
Hello
Hello

μ°Έκ³ 

https://kotlinlang.org/docs/delegated-properties.html#lazy-properties

λΉŒλ”μ™€ μ‘°μ •μž

λ©”μ„œλ“œ 넀이밍에 λŒ€ν•œ 리뷰λ₯Ό λ°›μ•˜λ‹€. 사싀 λ©”μ„œλ“œλͺ…은 μ΅œλŒ€ν•œ κ°€λ…μ„±μžˆκ²Œ μž‘μ„±ν•˜λ €κ³  λ…Έλ ₯ν–ˆμ§€λ§Œ λ°˜ν™˜ νƒ€μž…μ— λŒ€ν•΄μ„œλŠ” μ‹ μ€‘ν•˜κ²Œ μƒκ°ν•˜μ§€ μ•Šμ•˜λ˜ 것 κ°™λ‹€.

μ—˜λ ˆκ°•νŠΈ 였브젝트 μ±… λ‚΄μš© 쀑 쒋은 λ©”μ„œλ“œ 넀이밍을 ν•˜λŠ” 방법이 μ œμ‹œλ˜μ–΄ μžˆλ‹€.

λΉŒλ”μ™€ μ‘°μ •μž 에 λŒ€ν•œ λ‚΄μš©μ΄μ—ˆλŠ”λ°

  • λΉŒλ”
    • λΉŒλ”λŠ” λ­”κ°€λ₯Ό λ§Œλ“€κ³  μƒˆλ‘œμš΄ 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œλ₯Ό λ§ν•œλ‹€. λ°˜ν™˜ νƒ€μž…μ€ μ ˆλŒ€ voidκ°€ 될 수 μ—†μœΌλ©° λͺ…사, ν˜•μš©μ‚¬λ‘œ ν‘œν˜„ν•œλ‹€.
fun pow(base: Int, power: Int): Int
fun employee(id: Int): Employee
fun parsedCell(x: Int, y: Int): String
fun bytedSaved(content: String): Int
  • μ‘°μ •μž
    • μ‘°μ •μžλŠ” 객체둜 μΆ”μƒν™”ν•œ μ—”ν‹°ν‹°λ₯Ό μˆ˜μ •ν•˜λŠ” λ©”μ„œλ“œλ₯Ό λ§ν•œλ‹€. λ°˜ν™˜ νƒ€μž…μ€ 항상 void이며 이름은 항상 동사이닀. 
    • μ‘°μ •μžμ˜ 경우 의미λ₯Ό μ’€ 더 ν’λΆ€ν•˜κ²Œ μ„€λͺ…ν•˜κΈ° μœ„ν•΄μ„œ 뢀사 ν™œμš©μ΄ κ°€λŠ₯ν•˜λ‹€.
fun save(content: String)
fun put(key: String, value: Float)
fun remote(emp: Employee)
fun quicklyPrint(id: Int)

 

- 객체지ν–₯적인 μ ‘κ·Όλ²•μœΌλ‘œ λ©”μ„œλ“œλͺ…을 잘 μ§€μœΌλ €λ©΄ μ–΄λ–€ 것을 λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ 이름을 λ™μ‚¬λ‘œ 짓기 λ³΄λ‹€λŠ” λͺ…μ‚¬λ‘œ 짓고(λΉŒλ”) 무언가λ₯Ό λ³€κ²½ν•  λ•Œμ—λŠ” 동사(μ‘°μ •μž)λ₯Ό μ‚¬μš©ν•˜μž
- λ©”μ„œλ“œ λͺ…을 μž‘λͺ…ν•˜κΈ° μ–΄λ ΅λ‹€λ©΄ κ·Έ λ©”μ„œλ“œμ—μ„œ λͺ©ν‘œν•˜λŠ” μž‘μ—…μ΄ ν•˜λ‚˜κ°€ 아닐 κ°€λŠ₯성이 λ†’λ‹€.
- Boolean 값을 λ°˜ν™˜ν•˜λŠ” λΉŒλ”λŠ” μ˜ˆμ™Έ. 이 κ²½μš°μ—” ν˜•μš©μ‚¬

μ°Έκ³ 

https://lovethefeel.tistory.com/163?category=1002227

https://jessyt.tistory.com/141

Functional Interface

좔상 λ©”μ„œλ“œκ°€ ν•˜λ‚˜λ§Œ μžˆλŠ” μΈν„°νŽ˜μ΄μŠ€μ— λŒ€ν•΄ functional interface λ₯Ό μ μš©ν•  수 μžˆλ‹€.

fun interface CardGenerator {
    fun generate(): Card
}
val randomCardGenerator = CardGenerator {
    Card(Rank.random(), Suit.random())
}

μœ„μ™€ 같이 functional interface λ₯Ό μ μš©ν•˜λ©΄ κ°„κ²°ν•œ ν•¨μˆ˜ν˜• μ½”λ“œ μž‘μ„±μ΄ κ°€λŠ₯해지며 짧은 λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λΆˆν•„μš”ν•œ μ½”λ“œλ₯Ό 쀄일 수 μžˆλ‹€.

μ°Έκ³ 

https://kotlinlang.org/docs/fun-interfaces.html

Step3

https://github.com/next-step/kotlin-blackjack/pull/554

일급 μ»¬λ ‰μ…˜κ³Ό λΆˆλ³€ 객체

일급 μ»¬λ ‰μ…˜μ„ λ§Œλ“€μ—ˆλ‹€κ³  ν•΄μ„œ λ°˜λ“œμ‹œ λ‚΄λΆ€κ°€ λΆˆλ³€ 객체일 ν•„μš”λŠ” μ—†λ‹€. ν•˜μ§€λ§Œ ν•˜λ‚˜μ˜ ν”„λ‘œνΌν‹°λ₯Ό 가지기 λ•Œλ¬Έμ— λΆˆλ³€κ°μ²΄λ‘œ λ§Œλ“€μ—ˆμ„ λ•Œμ˜ μž₯점이 λšœλ ·ν•΄ μ•„λž˜μ™€ 같이 λΆˆλ³€ 객체둜 ν•¨κ»˜ κ°€μ Έκ°€λŠ” 것이 μ’‹λ‹€.

data class Cards(
    val values: List<Card> = emptyList()
) {
    constructor(vararg card: Card) : this(values = card.toList())

    val sumOfScoreWithAceAsOne: Int by lazy {
        values.sumOf { it.getScore() }
    }

    val numberOfAce: Int by lazy {
        values.count { it.rank == Rank.ACE }
    }
}

λ¦¬λ·°μ–΄λ‹˜μ€ λΆˆλ³€κ°μ²΄λ‘œ λ§Œλ“œλŠ” 것 μžμ²΄λŠ” 쒋은 μ‹œλ„μ΄μ§€λ§Œ ν˜„μ‹€ μ„Έκ³„μ˜ κ΄€μ μ—μ„œ λ³΄μ•˜μ„ λ•Œ ν”Œλ ˆμ΄μ–΄λ‚˜ λ”œλŸ¬κ°€ λ“€κ³  μžˆλŠ” μΉ΄λ“œ 더미 λ˜ν•œ λ³€ν•  수 μžˆμ–΄ λ¬΄λ¦¬ν•˜κ²Œ λΆˆλ³€μœΌλ‘œ λ§Œλ“€ ν•„μš”λŠ” μ—†μ–΄λ³΄μΈλ‹€λŠ” μ˜κ²¬μ„ 말씀해주셨닀.

ν˜„μ‹€μ—μ„œ ν”Œλ ˆμ΄μ–΄λ‚˜ λ”œλŸ¬κ°€ λ“€κ³  μžˆλŠ” μΉ΄λ“œ 더미가 λ³€ν•  수 μžˆλŠ” μš”κ΅¬μ‚¬ν•­μ΄κΈ° λ•Œλ¬Έμ— λ¬΄λ¦¬ν•˜κ²Œ λΆˆλ³€μœΌλ‘œ λ§Œλ“€μ§€λŠ” μ•Šμ•„λ„ 될 것 κ°™λ‹€λŠ” μ˜κ²¬μ— κ³΅κ°ν–ˆκ³  λΆˆλ³€κ°μ²΄λ₯Ό μš°μ„ μ μœΌλ‘œ μƒκ°ν•˜λ˜, μœ μ—°ν•˜κ²Œ κ°€λ³€κ°μ²΄μ˜ κ΄€μ μ—μ„œ μƒκ°ν•΄λ³΄λŠ” 것도 쒋을 것 κ°™λ‹€.

Step4

https://github.com/next-step/kotlin-blackjack/pull/568

μ»¬λ ‰μ…˜μ˜ 변경지점

class Cards(
    values: List<Card> = emptyList(),
) {
    var values: List<Card> = values
        private set

    constructor(vararg card: Card) : this(values = card.toMutableList())

    val sumOfScoreWithAceAsOne: Int
        get() = values.sumOf { it.getScore() }

    val numberOfAce: Int
        get() = values.count { it.rank == Rank.ACE }

    fun add(card: Card) {
        values = values + card
    }

    fun addAll(cards: List<Card>) {
        values = values + cards
    }
}

Cards 클래슀λ₯Ό κ°€λ³€ν•˜κ²Œ λ³€κ²½ν•˜λ©΄μ„œ mutableList λ₯Ό λ§Œλ“€κ³  방어적 λ³΅μ‚¬λ‘œ λ°˜ν™˜ν• μ§€, λ˜λŠ” var (private set) 둜 ν”„λ‘œνΌν‹° 자체λ₯Ό λ³€κ²½ μ§€μ μœΌλ‘œ 두고 addλ₯Ό ν•  λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ 리슀트λ₯Ό λ§Œλ“€μ§€ 고민을 ν–ˆλ‹€.

λ¦¬λ·°μ–΄λ‹˜λ„ 이 뢀뢄에 λŒ€ν•΄ 리뷰λ₯Ό ν•΄μ£Όμ…¨κ³  valuesλŠ” MutableList둜 두고, 외뢀에 Listλ₯Ό λ°˜ν™˜ν•˜λŠ” getterλ₯Ό λ…ΈμΆœμ‹œν‚¨ 후에 μ ‘κ·Όν•  λ•Œ valuesλ₯Ό 볡제(copy)ν•œ Listλ₯Ό λ§Œλ“€μ–΄μ„œ λ°˜ν™˜ν•΄λ„ 쒋을 것 κ°™λ‹€λŠ” μ˜κ²¬μ„ μ£Όμ…¨λ‹€.

var λ₯Ό 톡해 ν”„λ‘œνΌν‹° 자체λ₯Ό λ³€κ²½ μ§€μ μœΌλ‘œ λ‘μ—ˆμ„ λ•Œκ°€ mutableList λ‘œ 리슀트 내뢀에 변경지점을 λ‘λŠ” 것보닀 λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ μ•ˆμ •μ„±μ΄ 크기 λ•Œλ¬Έμ— ν”„λ‘œνΌν‹° 자체λ₯Ό λ³€κ²½ μ§€μ μœΌλ‘œ λ‘λŠ” 방식을 μ„ νƒν–ˆλ‹€.

λ‹€λ§Œ ν˜„μž¬ λ―Έμ…˜μ—μ„œ μŠ€λ ˆλ“œ μ•ˆμ •μ„±μ„ κ³ λ €ν•΄μ•Όν•  상황은 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— 속도가 더 λΉ λ₯Έ mutableListλ₯Ό μ‚¬μš©ν•˜κ³  방어적 λ³΅μ‚¬λ‘œ λ¦¬ν„΄ν•˜λŠ” 것도 쒋은 방법인 것 κ°™λ‹€.