songining
article thumbnail

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๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋กœ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ธ ๊ฒƒ ๊ฐ™๋‹ค.