Kotest

  1. ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ ์ž‘์„ฑ

  2. ๊ทธ ๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ AVD๋‚˜ ๋””๋ฐ”์ด์Šค์—์„œ ๋นŒ๋“œํ•˜์—ฌ ๊ธฐ๋Šฅ ํ™•์ธ

  3. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฝ”๋“œ ์ˆ˜์ • ํ›„ ๋‹ค์‹œ ๋นŒ๋“œํ•˜์—ฌ ํ™•์ธ

์œ„์™€ ๊ฐ™์€ ์ˆœ์„œ๋ฅผ ๋ฐ˜๋ณตํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๊ฐ€ ์ž‘๋‹ค๋ฉด ์œ„์™€ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๋ฐ˜๋ณตํ•ด๋„ ๊ทธ๋ ‡๊ฒŒ ํฌ๊ฒŒ ์ฐจ์ด๊ฐ€ ๋‚˜์ง€ ์•Š๋Š”๋‹ค.. ๊ทธ๋Ÿฌ๋‚˜ But ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง„๋‹ค๋ฉด??!! ์•ฑ์„ ๋นŒ๋“œํ•˜๋Š” ์‹œ๊ฐ„ + UI์— ์ž…๋ ฅํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•˜๋Š” ์‹œ๊ฐ„์ด ๋”ํ•ด์ ธ ๋งŽ์€ ์‹œ๊ฐ„์„ ํ•„์š”๋กœ ํ•  ๊ฒƒ์ด๋‹ค.

ํ˜„์žฌ ํšŒ์‚ฌ์—์„œ๋งŒ ํ•ด๋„ ์•ฑ ๋นŒ๋“œ ํ•˜๋Š” ๋ฐ๋งŒ 2~3๋ถ„์ด ์†Œ์š”๋˜๊ณ  ๋˜ ์ถ”๊ฐ€ํ•œ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๋Š”๋ฐ ์ถ”๊ฐ€์ ์ธ ์‹œ๊ฐ„์ด ํ•„์š”ํ•˜๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•จ์œผ๋กœ์จ ์–ป๋Š” ์ด์ ์„ ์‚ดํŽด๋ณด๋ฉด

  • ์ž˜๋ชป๋œ ๋ถ€๋ถ„์„ ๋น ๋ฅด๊ฒŒ ํ™•์ธ ๊ฐ€๋Šฅ

  • ๋””๋ฒ„๊น… ์‹œ๊ฐ„ ๋‹จ์ถ•

  • ๋ชจ๋“ˆ์ด ์˜๋„ํ•œ๋Œ€๋กœ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธ ๊ฐ€๋Šฅ

  • ํด๋ฆฐํ•œ ๊ตฌ์กฐ์˜ ๊ฐœ๋ฐœ ๊ฐ€๋Šฅ

์œ„์™€ ๊ฐ™์€ ์žฅ์ ์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๋Š” ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

์šฐ์„  ์•Œ์•„์•ผ ํ• ๊ฒŒ ์•ˆ๋“œ๋กœ์ด๋“œ๋Š” ๋””๋ฐ”์ด์Šค ์˜์กด๋„๊ฐ€ ๊ฐ•ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ผ ์•ˆ๋“œ๋กœ์ด๋“œ OS๊ฐ€ ์„ค์น˜๋œ ๋””๋ฐ”์ด์Šค์—์„œ๋งŒ ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— JVM์ด ์‹คํ–‰ํ•˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ Unit Test์—์„œ ์•ˆ๋“œ๋กœ์ด๋“œ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. (Mocking ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ํ…Œ์ŠคํŠธ)

์ด์™€ ๊ฐ™์€ ์ด์œ ๋กœ ์ตœ๋Œ€ํ•œ ์•ˆ๋“œ๋กœ์ด๋“œ ํ”„๋ ˆ์ž„์›Œํฌ์— ์˜์กด์„ฑ์ด ์—†๋Š” ์ฝ”๋“œ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.(DI, MVVM, Clean Architecture๋“ฑ)


์•ˆ๋“œ๋กœ์ด๋“œ์—๋Š” ๋กœ์ปฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test)์™€ ๊ณ„์ธก ํ…Œ์ŠคํŠธ(Instrumentation Test) ์ด ๋‘ ๊ฐ€์ง€ ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ๋‹ค.

๋กœ์ปฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test)

  • module-name/src/test/java/ ํ•˜์œ„์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

  • ํ…Œ์ŠคํŠธ ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค

  • JVM์—์„œ ์‹คํ–‰๋˜๋Š” ํ…Œ์ŠคํŠธ

  • ์•ˆ๋“œ๋กœ์ด๋“œ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์ข…์†์„ฑ์ด ์—†๊ฑฐ๋‚˜ ๋ชจ์˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ์ด ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ

  • JUnit, Mockito, PowerMock, Truth, Robolectric, Kotest

๊ณ„์ธก ํ…Œ์ŠคํŠธ(Instrumentation Test)

  • module-name/src/androidTest/java/ ํ•˜์œ„์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

  • ์•ˆ๋“œ๋กœ์ด๋“œ ํ”„๋ ˆ์ž„์›Œํฌ์— ์ข…์†์„ฑ์ด ์žˆ๋Š” ํ…Œ์ŠคํŠธ

  • ์‹ค์ œ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ธฐ๊ธฐ๋‚˜ ์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ์‹คํ–‰๋˜๋Š” ํ…Œ์ŠคํŠธ

  • Espresso, UIAnimator, Robotium, Appium, Calabash

์ด ์ค‘์—์„œ ์ฝ”ํ‹€๋ฆฐ์„ ์‚ฌ์šฉํ•˜์—ฌ Unit Test๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” Kotest๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๋ ค ํ•œ๋‹ค.


Kotest

Kotest๋Š” Kotlin์„ ์‚ฌ์šฉํ•˜์—ฌ Unit Test๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์šฐ์„ 

1. ์˜์กด์„ฑ ์ถ”๊ฐ€

android.testOptions {
   unitTests.all {
      useJUnitPlatform()
   }
}

testImplementation 'io.kotest:kotest-runner-junit5:5.0.3'

App๋‹จ์˜ Gradle์— ์œ„์™€ ๊ฐ™์€ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

2. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ์œ„์น˜

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‚˜ ์ƒˆ๋กœ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ ์œ„์˜ ExampleUnitTest ์œ„์น˜์— ํ…Œ์ŠคํŠธํ•  ์ฝ”ํ‹€๋ฆฐ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ๋œ๋‹ค.

3. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

์šฐ์„  Kotest์—์„œ๋Š” Spec์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ ์Šคํƒ€์ผ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ค‘์— ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ๊ฑด StringSpec ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜๋Š”๋ฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” init ์•ˆ์— ์ž‘์„ฑํ•˜๊ณ  ๊ทธ ์•ˆ์— ์ฝ”๋“œ์˜ Description, ํ…Œ์ŠคํŠธํ•  ์ฝ”๋“œ์ˆœ์œผ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

StringSpec

class StringTest: StringSpec() { // StringSpec์„ ์ƒ์†
    init { // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ
        "strings.length should return size of string" {  // ์ฝ”๋“œ ์„ค๋ช… - ํ…Œ์ŠคํŠธ์— ๊ด€์—ฌx
            "hello".length shouldBe 5   // hello ๋ฌธ์ž์—ด์˜ ๊ธธ์ด๊ฐ€ 5์ธ์ง€ ์ฒดํฌ
        }
    }
}

Kotest์—๋Š” StringSpec ๋ง๊ณ ๋„ ์—ฌ๋Ÿฌ ์ŠคํŽ™์ด ์žˆ๋Š”๋ฐ ์ฝ๋Š” ๋ฐฉ์‹๋งŒ ๋‹ค๋ฅผ ๋ฟ ์ž‘์„ฑํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„  ์ทจํ–ฅ

๋ช‡ ๊ฐ€์ง€ ๋” ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด

FunSpec

class FunSpecTest: FunSpec() { // ํ•จ์ˆ˜ ํ˜•ํƒœ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
    init {
        test("String length should return length of the string") { // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์„ค๋ช… - description
            "sanggun".length shouldBe 7 // ๋ฌธ์ž์—ด์˜ ๊ธธ์ด๋ฅผ ์ฒดํฌํ•˜๋Š” ์ฝ”๋“œ
            "hello".length shouldBe 5
        }
    }
}

AnnotationSpec

class AnnotationTest : AnnotationSpec() { // ์–ด๋…ธํ…Œ์ด์…˜ ํ˜•ํƒœ๋กœ ์ž‘์„ฑ, JUnit๊ณผ ๋น„์Šทํ•˜๋‹ค.

    @BeforeEach
    fun beforeTest() {
        println("Before each test")
    }

    @Test
    fun test1() {
        1 shouldBe 1
    }

    @Test
    fun test2() {
        3 shouldBe 3
    }
}

๋“ฑ๋“ฑ ๋งŽ์€ Spec ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ๊ฑด ์—ฌ๊ธฐ๐Ÿ‘ˆ

๋‹ค์Œ์€ Matcher์ž…๋‹ˆ๋‹ค. Matcher๋ž€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ ๋„์™€์ฃผ๋Š” ์š”์†Œ๋“ค์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด, ์•„๋ž˜ ์ฝ”๋“œ์—์„œ shouldBe๋Š” ๋™์ผํ•จ์„ ์ฒดํฌํ•ด์ฃผ๋Š” Matcher์ž…๋‹ˆ๋‹ค.

"sanggun".length shouldBe 7

Matcher๋„ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ

class MatcherTest : StringSpec() {
    init {
    -----------------------String Matchers-------------------------
        // 'shouldBe'๋Š” ๋™์ผํ•จ์„ ์ฒดํฌํ•˜๋Š” Matcher
        "hello world" shouldBe haveLength(11) // length๊ฐ€ 11์ด์–ด์•ผ ํ•จ์„ ์ฒดํฌ
        "hello" should include("ll") // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ์ฒดํฌ
        "hello" should endWith("lo") // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋์˜ ํฌํ•จ๋˜๋Š”์ง€ ์ฒดํฌ
        "hello" should match("he...") // ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋งค์นญ๋˜๋Š”์ง€ ์ฒดํฌ
        "hello".shouldBeLowerCase() // ์†Œ๋ฌธ์ž๋กœ ์ž‘์„ฑ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ

-----------------------Collection Matchers-------------------------
        val list = emptyList<String>()
        val list2 = listOf("a", "b", "c")
        val map = mapOf<String, String>(Pair("a", "1"))

        list should beEmpty() // ์›์†Œ๊ฐ€ ๋น„์—ˆ๋Š”์ง€ ์ฒดํฌ ํ•ฉ๋‹ˆ๋‹ค.
        list2 shouldBe sorted<String>() // ํ•ด๋‹น ์ž๋ฃŒํ˜•์ด ์ •๋ ฌ ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
        map should contain("a", "1") // ํ•ด๋‹น ์›์†Œ๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
        map should haveKey("a") // ํ•ด๋‹น key๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
        map should haveValue("1") // ํ•ด๋‹น value๊ฐ€ ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
    }
}

์œ„์™€ ๊ฐ™์ด ๋งŽ์€ Matcher๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

Last updated