ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spock Test Framework ์ •๋ฆฌ
    Hello, World ๐Ÿ’ป/Test โœ… 2020. 12. 7. 23:54

    JUnit 5 ์„ ์ข€ ๋” ์„ธ์„ธํ•˜๊ฒŒ ๊ณต๋ถ€ํ•ด๋ณผ๊นŒ ํ•˜๋‹ค ์ƒˆ๋กœ์šด ์–ธ์–ด์™€ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์–ด์„œ Spock Framework ๋ฅผ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

    Spock ๋Š” Java, Groovy ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋‹ค. BDD(Behaviour-Driven Development) ํ”„๋ ˆ์ž„์›Œํฌ๋ผ๊ณ ๋„ ํ•œ๋‹ค. Groovy ์–ธ์–ด๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ์•„๋ฆ„๋‹ต๊ณ  ํ‘œํ˜„๋ ฅ์ด ๋›ฐ์–ด๋‚˜๋‹ค๊ณ  ์†Œ๊ฐœํ•˜๊ณ  ์žˆ๋‹ค(ใ…Žใ…Ž). JUnit Runner ๋•๋ถ„์— ๋Œ€๋ถ€๋ถ„์˜ IDE, ๋นŒ๋“œ ํˆด, CI ์„œ๋ฒ„์—์„œ ๊ตฌ๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

    ์งง๊ฒŒ ์ผ์ฃผ์ผ์ •๋„ Spock ๋ฅผ ์ข€ ์จ๋ณด๊ณ  ๋Š๋‚€ ์ ์€ ํ™•์‹คํžˆ JUnit ๋ณด๋‹ค ๋” ์‰ฝ๊ณ  ๋ช…์‹œ์ ์ธ ๋Š๋‚Œ์ด ์žˆ์—ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด JUnit ์„ ์“ธ ๋•Œ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์— ์ฃผ์„์œผ๋กœ given, when, then ์ด๋ผ๊ณ  ์ž‘์„ฑํ–ˆ๋Š”๋ฐ, Spock ์—์„œ๋Š” ๊ฐ๊ฐ์„ ๋ธ”๋ก์ด๋ผ๊ณ  ํ•˜๊ณ  given: when: ์ด๋ ‡๊ฒŒ ๋ธ”๋ก์„ ๋„ฃ์–ด์ค˜์•ผ ๋™์ž‘ํ•œ๋‹ค. ์ข€ ๋” ๋ช…์‹œ์ ์œผ๋กœ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜๋‹ค. ๋˜ JUnit ์€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ์—ฌ๋Ÿฌ ํŒจํ‚ค์ง€์— ์กด์žฌํ•ด์„œ ์›ํ•˜๋Š” ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์™ธ์›Œ์„œ ์‚ฌ์šฉํ–ˆ์–ด์•ผ ํ–ˆ๋Š”๋ฐ, Spock ๋Š” All In One ์Šค๋Ÿฌ์šด ๋Š๋‚Œ์ด ์žˆ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Spock ์˜ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋Š” ๋ณด๊ณ  ๊นœ์ง ๋†€๋ž๋‹ค! ๋ณด๋ฉด ์•Œ๋ฆฌ๋ผ. ๋” ์“ฐ๋‹ค ๋ณด๋ฉด ์žฅ๋‹จ์ ์„ ๋Š๋ผ๊ฒ ์ง€๋งŒ ์ฒซ์ธ์ƒ์€ ๊ตฟ๊ตฟ์ด๋‹ค.

    Gradle ์˜์กด์„ฑ ์„ค์ •

    plugins {
        id 'java'
        id 'groovy'
    }
    
    apply plugin: 'groovy'
    
    group 'me.ohhhmycode'
    version '1.0-SNAPSHOT'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'
        testCompile group: 'org.spockframework', name: 'spock-core', version: '1.3-groovy-2.5'
    }

     

    - spock-core ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. (mvnrepository.com)
    - ๊ฐœ์ธ์ ์œผ๋กœ Intellij ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ฒ„์ „์„ ์‚ฌ์šฉ ์ค‘์ธ๋ฐ, plugins ์„ค์ •์„ ํ•ด์ค˜์•ผ Test ๋ฅผ ์ž‘์„ฑํ–ˆ์„ ๋•Œ 'No tasks available' ๊ฐ€ ๋œจ์ง€ ์•Š๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

    ์˜์กด์„ฑ ์„ค์ •๊ณผ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ •์ด ๋๋‚˜๋ฉด /src/test/groovy ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  groovy ๋””๋ ‰ํ† ๋ฆฌ์— ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.


    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        def "์ฒซ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ"() {
            given:
            def x = 5;
            def y = 10;
    
            when:
            def sum = x + y;
    
            then:
            sum == 15;
        }
    
    }

     

    ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•ด๋ดค๋‹ค. Specification ๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ๋ณด์ด๋Š”๋ฐ spock ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋Š” spock.lang ํŒจํ‚ค์ง€์˜ Specification ์„ ์ƒ์†๋ฐ›์•„์•ผ ํ•œ๋‹ค. Specification ํด๋ž˜์Šค์—๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ์ค‘์š”ํ•œ ๊ฒƒ๋“ค์ด ๋Œ€๋ถ€๋ถ„ ์žˆ๋‹ค.

    ์•„๋ž˜๋ถ€ํ„ฐ๋Š” ๋ฌธ์„œ ์š”์•ฝ๊ณผ ์ฝ”๋“œ ์˜ˆ์ œ๋กœ ํ›‘์–ด๋ณด๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

    Specifications

    Spock ์—์„œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ Specification ์„ ์ž‘์„ฑํ•œ๋‹ค๊ณ  ํ•˜๋Š”๋ฐ, Specifications ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์–ด๋–ค ๊ธฐ๋Šฅ(feature)์— ๋Œ€ํ•ด ์˜ˆ์ƒ๋˜๋Š” ๋™์ž‘๋“ค์„ ์ž‘์„ฑํ•œ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ Spock๋Š” ์—์„œ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ Feature Method ๋ผ๊ณ  ํ•œ๋‹ค.

    ์œ„์—์„œ ๋งํ•œ ๊ฒƒ์ฒ˜๋Ÿผ Specification ํด๋ž˜์Šค์— ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒƒ์ด ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Specification ์—๋Š” Sputnik ๋ผ๋Š” JUnit Runner ๊ฐ€ ์žˆ๋Š”๋ฐ, Sputnik ๋กœ Java ๊ฐœ๋ฐœ ํˆด๋“ค์ด๋‚˜ ๋นŒ๋“œ ํˆด์—์„œ Spock ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋ผ๊ณ  ํ•œ๋‹ค.

    Fields

    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        static def classStore = new HashSet();
        def myClass = new MyClass();
    
        def "ํ•„๋“œ ํ…Œ์ŠคํŠธ 1"() {
            when:
            classStore.add(myClass);
    
            then:
            classStore.size() == 1
            println myClass // spocktest.MyClass@4a35cdc8
        }
    
        def "ํ•„๋“œ ํ…Œ์ŠคํŠธ 2"() {
            when:
            classStore.add(myClass)
    
            then:
            classStore.size() == 2
            println myClass // spocktest.MyClass@6915ed51
        }
    
    }
    

    ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•œ๋‹ค. (Feature Method ๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์œ„์—์„œ ์•„๋ž˜๋กœ ์‹คํ–‰๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค(?) ํ™•์ธํ•ด๋ด์•ผ์ง€..)

    classStore, myClass ๊ฐ€ ์„ ์–ธ๋˜์–ด ์žˆ๋Š” ์˜์—ญ์„ Instance Fields ๋ผ๊ณ  ๋ถ€๋ฅด๋Š”๋ฐ ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด๋“ค์„ ์„ ์–ธํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ๋œ๋‹ค. (setup() ๋ฉ”์„œ๋“œ์—์„œ ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผ) ์•Œ์•„๋‘ฌ์•ผ ํ•  ๊ฒƒ์€ ์ธ์Šคํ„ด์Šค ํ•„๋“œ์—์„œ ์„ ์–ธํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•œ ๊ฐ์ฒด๋“ค์€ ๊ฐ๊ฐ์˜ Feature Method ์—์„œ ๊ณต์œ ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋ฉ”์„œ๋“œ๋Š” ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ๋Š” ๋งค๋ฒˆ ๋ถ„๋ฆฌ๋œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

    ๊ฐ์ฒด๋ฅผ ๋ชจ๋“  Feature Method ์—์„œ ๊ณต์œ ํ•ด ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ @Shared ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด ๋œ๋‹ค. (@setupSpec() ๋ฉ”์„œ๋“œ์—์„œ ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผ) ์•„๋ž˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด์ž.

    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        static def classStore = new HashSet();
        @Shared def myClass = new MyClass();
    
        def "ํ•„๋“œ ํ…Œ์ŠคํŠธ 1"() {
            when:
            classStore.add(myClass);
    
            then:
            classStore.size() == 1
            println myClass // spocktest.MyClass@217f3ce1
        }
    
        def "ํ•„๋“œ ํ…Œ์ŠคํŠธ 2"() {
            when:
            classStore.add(myClass)
    
            then:
            classStore.size() == 1
            println myClass // spocktest.MyClass@217f3ce1
        }
    
    }
    

     

    classStore ์ฒ˜๋Ÿผ static ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ Feature Method ์—์„œ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋งŒ static ๋ณ€์ˆ˜๋Š” ์ •๋ง ์ƒ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ๊ทธ๊ฒŒ ์•„๋‹ˆ๋ฉด @Shared ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋‹ค. ์œ„์—์„œ classStore ๋„ ์‚ฌ์‹ค @Shared ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ๋งž์„ ๊ฒƒ ๊ฐ™๋‹ค.

    Fixture Methods

    def setupSpec() {}    // ์ฒซ๋ฒˆ์งธ feature method ์‹คํ–‰ ์ „์— ํ•œ๋ฒˆ ์‹คํ–‰
    def setup() {}        // ๋ชจ๋“  feature method ์‹คํ–‰ ์ „์— ๋งค๋ฒˆ ์‹คํ–‰
    def cleanup() {}      // ๋ชจ๋“  feature method ์‹คํ–‰ ํ›„์— ๋งค๋ฒˆ ์‹คํ–‰
    def cleanupSpec() {}  // ๋งˆ์ง€๋ง‰ feature method ์‹คํ–‰ ํ›„์— ํ•œ๋ฒˆ ์‹คํ–‰

     

    Fixture Methods ๋Š” ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ์„ธํŒ…ํ•˜๊ฑฐ๋‚˜ ์ •๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ํ•„์ˆ˜๋Š” ์•„๋‹ˆ๋ฏ€๋กœ ์„ ํƒ์ ์œผ๋กœ ํ•˜๋ฉด ๋œ๋‹ค. 

    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        @Shared def sharedObj;
        def obj;
    
        def setupSpec() {
            sharedObj = new MyClass()
        }
    
        def setup() {
            obj = new MyClass()
        }
        
        // feature methods..
    }

    setupSpec() ์—์„œ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๋ณ€์ˆ˜๋“ค์€ @Shared ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋“ ๊ฐ€, static ๋ณ€์ˆ˜์ด์–ด์•ผ ํ•œ๋‹ค. 
    ์•„๋ž˜๋Š” Fixture Methods ๋“ค์ด ์‹คํ–‰๋˜๋Š” ์ˆœ์„œ์ด๋‹ค.

    1. super.setupSpec
    2. sub.setupSpec
    3. super.setup
    4. sub.setup
    5. feature method
    6. sub.cleanup
    7. super.cleanup
    8. sub.cleanupSpec
    9. super.cleanupSpec

    Feature Methods

    import spock.lang.*
    
    class MySpockTest extends Specification {
        
        def "ํ•œ๊ธ€๋กœ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค"() {
        }
    
    }
    

    ๊ฐ€์žฅ ์ค‘์š”ํ•œ Feature Methods ์ด๋‹ค. ์œ„์—์„œ ๋งํ–ˆ๋“ฏ, Feature Method ๋Š” ๊ฐœ๋ฐœํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด Spec ๋Œ€๋กœ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•ด ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ด€๋ก€์ ์œผ๋กœ ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด๋กœ Feature Method ์˜ ์ด๋ฆ„์„ ์ง“๋Š”๋‹ค๊ณ  ํ•œ๋‹ค. JUnit ์„ ์‚ฌ์šฉํ•  ๋•Œ๋ณด๋‹ค ์ข€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ ๋ช…์„ ์ง€์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

    Feature Method ๋Š” 4๋‹จ๊ณ„๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.

    1. Set Fixture
    2. ํ”„๋กœ๊ทธ๋žจ ๊ธฐ๋Šฅ ์ˆ˜ํ–‰ (Stimulus)
    3. ํ”„๋กœ๊ทธ๋žจ ๊ธฐ๋Šฅ ์ˆ˜ํ–‰ ํ›„์˜ Response ํ™•์ธ (Describe the response)
    4. Clear Fixture

    1, 4๋‹จ๊ณ„๋Š” ์œ„์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ ์„ ํƒ์ ์œผ๋กœ ํ•˜๋ฉด ๋œ๋‹ค. ๋ฌธ์„œ์— 2, 3 ๋‹จ๊ณ„๋Š” Stimulus to the system.., Describe the response.. ๋ผ๊ณ  ๋˜์–ด ์žˆ๋Š”๋ฐ ๋‚˜๋ฆ„๋Œ€๋กœ ํ’€์–ด์จ๋ดค๋‹ค..^^; stimulus, response ๋Š” ํ•ญ์ƒ ์กด์žฌํ•ด์•ผํ•˜๊ณ , ํ•œ๋ฒˆ ์ด์ƒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

    Block

    Spock ์€ ์œ„์—์„œ ๋งํ•œ 4๋‹จ๊ณ„๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋‚ด์žฅ๋œ ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค. ์ด๊ฑธ Block ๋ผ๊ณ  ํ•œ๋‹ค. 

    given, when, then, expect, cleanup, where ๋ผ๊ณ  ํ•˜๋Š” 6๊ฐœ์˜ ๋ธ”๋ก์ด ์žˆ๋‹ค. ๊ฐ ๋ธ”๋ก์€ given:, when: ๊ณผ ๊ฐ™์€ ๋ผ๋ฒจ๋กœ ์‹œ์ž‘๋˜๊ณ  ๋‹ค์Œ ๋ธ”๋ก์ด ๋‚˜์˜ฌ ๋•Œ๊นŒ์ง€์˜ ์˜์—ญ์ด ๋‹ค ํ•ด๋‹น ๋ธ”๋ก์˜ ์˜์—ญ์ด ๋œ๋‹ค.

    ๋ฉ”์„œ๋“œ์˜ ์‹œ์ž‘๋ถ€ํ„ฐ ์ฒซ ๋ฒˆ์งธ ๋ธ”๋ก์ด ๋‚˜์˜ฌ ๋•Œ๊นŒ์ง€์˜ ์˜์—ญ์€ given ๋ธ”๋ก์ด ๋œ๋‹ค. given ์ด ๊ฐ€์žฅ ๋จผ์ €์™€์•ผํ•˜๊ณ  given ์€ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช…์‹œ์ ์œผ๋กœ given: ์„ ์จ์ฃผ๋Š”๊ฒŒ ์ข‹์ง€ ์•Š์„๊นŒ ์‹ถ๋‹ค.

    Feature Method ๋Š” ์ ์–ด๋„ ํ•˜๋‚˜์˜ ๋ธ”๋ก์€ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๊ณ , ๋ธ”๋ก์„ ์ค‘์ฒฉํ•  ์ˆ˜๋Š” ์—†๋‹ค. ์•„๋ž˜ ๊ทธ๋ฆผ์„ ๋ณด๊ณ  ๊ฐ ๋‹จ๊ณ„์— ํ•ด๋‹นํ•˜๋Š” Block์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. where ๋Š” ์ข€ ํŠน์ˆ˜ํ•œ ์—ญํ• ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

    ์ถœ์ฒ˜: http://spockframework.org/spock/docs/1.3/images/Blocks2Phases.png

    Given Block

    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        def "๋”ํ•˜๊ธฐ ํ…Œ์ŠคํŠธ"() {
            given:
            def x = 10;
            def y = 20;
        }
    
    }

    Given ๋ธ”๋ก์—์„œ๋Š” ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ๊ฐ์ฒด๋‚˜ ํ™˜๊ฒฝ์„ ์ค€๋น„ํ•˜๋Š” ๊ณณ์ด๋‹ค. Given ๋ธ”๋ก ์•ž์— ๋‹ค๋ฅธ ๋ธ”๋ก์€ ์˜ฌ ์ˆ˜ ์—†๊ณ , Given: ๋ผ๋ฒจ์€ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋‹ค. given: ๋ผ๋ฒจ ๋Œ€์‹ ์— setup: ๋ผ๋ฒจ์„ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค.

    When, Then Block

    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        def "๋”ํ•˜๊ธฐ ํ…Œ์ŠคํŠธ"() {
            given:
            def x = 10;
            def y = 20;
    
            when: // stimulus
            def sum = x + y
    
            then: // response
            sum == 30
        }
    
    }
    

    when, then ๋ธ”๋ก์€ ํ•ญ์ƒ ๊ฐ™์ด ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ , ์—ฌ๋Ÿฌ๋ฒˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. when ์—์„œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ํ•˜๊ณ  -> then ์—์„œ ํ™•์ธํ•œ๋‹ค ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. when ๋ธ”๋ก์—์„œ๋Š” ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, ์ž„์˜๋กœ ๋ญ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ˜๋ฉด์—, then์€ ์กฐ๊ฑด ํ™•์ธ(Conditions), ์˜ˆ์™ธ ํ™•์ธ(Exception Conditions), ์ธํ„ฐ๋ž™์…˜(Interaction), ๋ณ€์ˆ˜ ์ •์˜์— ํ•ด๋‹นํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋„๋ก ์ œํ•œ๋˜์–ด ์žˆ๋‹ค.

    ์กฐ๊ฑด ํ™•์ธ, Conditions

    Conditions ์—์„œ๋Š” boolean ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•ด์„œ ์กฐ๊ฑดํ™•์ธ(์ƒํƒœํ™•์ธ)์„ ํ•œ๋‹ค. JUnit ์˜ assertions ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. then ์˜ ํ•œ์ค„ํ•œ์ค„์ด ์กฐ๊ฑด์„ ํ™•์ธํ•˜๋Š” assert ๋ฌธ์ด ๋œ๋‹ค. 

    package spocktest
    
    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        def "Stack ํ…Œ์ŠคํŠธ"() {
            given:
            def stack = new Stack();
            def number = 10;
    
            when:
            stack.push(number)
    
            then: // boolean ํ‘œํ˜„์‹์œผ๋กœ ์ž‘์„ฑ
            !stack.empty()
            stack.size() == 1
            stack.peek() == number
        }
    
    }
    
    Condition ์€ ํ•ญ์ƒ 1~5๊ฐœ ์ •๋„๋กœ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ํ•œ๋‹ค. ๊ทธ ์ด์ƒ์ด ๋˜๋ฉด Condition ๋“ค์ด ์ •๋ง ์—ฐ๊ด€๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ณ , ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ ๋” ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋Š”์ง€ ๋ด์•ผ ํ•œ๋‹ค.

    ์กฐ๊ฑด์ด ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ํ”ผ๋“œ๋ฐฑ์„ ์ค€๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ ํ”ผ๋“œ๋ฐฑ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๊ณ  ๋งŽ์ด ๋†€๋ž๋‹ค;

    package spocktest
    
    import spock.lang.*
    
    class MySpockTest extends Specification {
        def "Stack ํ…Œ์ŠคํŠธ"() {
            given:
            def stack = new Stack();
            def number = 10;
    
            when:
            stack.push(number)
    
            then:
            !stack.empty()
            stack.size() == 2 // ํ‹€๋ฆฐ ์กฐ๊ฑด
            stack.peek() == number;
        }
    }
    
    Condition not satisfied:
    
    stack.size() == 2
    |     |      |
    [10]  1      false
    
    	at spocktest.MySpockTest.Stack ํ…Œ์ŠคํŠธ(MySpockTest.groovy:29)

    stack ์˜ ํ˜„์žฌ ์‚ฌ์ด์ฆˆ๋„ ํ™•์ธํ•ด์„œ ๋ณด์—ฌ์ฃผ๋‹ค๋‹ˆ... ๐Ÿ‘

    ์˜ˆ์™ธ ํ™•์ธ, Exception Conditions

    package spocktest
    
    import spock.lang.*
    
    class MySpockTest extends Specification {
    
        def "Stack Exception ํ…Œ์ŠคํŠธ"() {
            given:
            def stack = new Stack();
    
            when:
            stack.pop()
    
            then:
            thrown(EmptyStackException)
            stack.empty()
        }
    
    }
    

    when ์—์„œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ then ์—์„œ thrown() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฑฐ์˜ ๊ทธ๋ƒฅ ์ฝ”๋“œ๋งŒ ์ž ๊น ์ฝ์œผ๋ฉด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์‰ฝ๊ฒŒ ๋˜์–ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค. then ๋ธ”๋ก์—์„œ ์ด๋ ‡๊ฒŒ ์˜ˆ์™ธ๋ฅผ ํ™•์ธํ•˜๋Š” ์กฐ๊ฑด์€ ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

    ์˜ˆ์™ธ ๊ฐ์ฒด๋ฅผ ๋ฐ›์œผ๋ ค๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ํ•˜๋ฉด ๋œ๋‹ค. ๋‘˜ ์ค‘์— ์„ ํƒํ•ด ์‚ฌ์šฉํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™์ง€๋งŒ, ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์ด ๋” ๋‚˜์€ ๊ฒƒ ๊ฐ™๋‹ค.

    when:
    stack.pop()
    
    then:
    def e = thrown(EmptyStackException)
    e.message == "error message"
    when:
    stack.pop()
    
    then:
    EmptyStackException e = thrown()
    e.message == "error message"

    ๋‘๋ฒˆ์งธ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ›์œผ๋ฉด ๊ฐ•ํƒ€์ž…์œผ๋กœ ์ฒดํฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , IDE ์—์„œ ์ฝ”๋“œ ์ž๋™ ์™„์„ฑ ์ง€์›๋„ ๋” ์ž˜ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ข€ ๋” ๊ฐ€๋…์„ฑ์ด ์ข‹์€ ์ฝ”๋“œ์ธ ๊ฒƒ ๊ฐ™๋‹ค. 

    ๋งŒ์•ฝ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„์•ผ ํ•  ์˜ˆ์™ธ ์ฒดํฌ๋„ ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ notThrown() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

    then:
    notThrown(NullPointerException)

     

    Interations

    ์ธํ„ฐ๋ž™์…˜์€ Mock ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด ํŠน์ • ๋ฉ”์„œ๋“œ๊ฐ€ ๋ช‡ ๋ฒˆ ํ˜ธ์ถœ๋˜๋Š”์ง€, ๋ฉ”์†Œ๋“œ์˜ ๋ฐ˜ํ™˜ ๊ฐ’์„ Stubbing ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ธํ„ฐ๋ ‰์…˜์— ๋Œ€ํ•ด์„œ๋Š” ๋‚ด์šฉ์ด ์ •๋ง ๋งŽ์€ ๊ฒƒ ๊ฐ™๋‹ค. ์ง€๊ธˆ์€ ์•„๋ž˜ ์˜ˆ์ œ๋งŒ ๋ณด๊ณ  ๋„˜์–ด๊ฐ€๊ณ  Interaction์€ ๋‚  ์žก๊ณ  ๋˜ ๋ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค.

    then:
    1 * subscriber.receive("hello") // 1๋ฒˆ ํ˜ธ์ถœ
    subscriber.receive(_) >> "ok" // receive(_) ์ด ํ˜ธ์ถœ๋œ ๊ฒฝ์šฐ "ok" ๋ฐ˜ํ™˜
    subscriber.receive(_) >> { throw new InternalError("ouch") } // receive(_) ์ด ํ˜ธ์ถœ๋œ ๊ฒฝ์šฐ ์˜ˆ์™ธ ๋ฐœ์ƒ

     

    Expect Blocks

    then ๋ธ”๋ก๊ณผ ๋น„๊ตํ•ด์„œ Expect ๋ธ”๋ก์€ ์กฐ๊ฑด ํ™•์ธ, ๋ณ€์ˆ˜ ์ •์˜๋งŒ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ์ข€ ๋” ์ œํ•œ์ ์ด๋‹ค. Expect ๋ธ”๋ก์€ ์ฝ”๋“œ ๋™์ž‘ => ์กฐ๊ฑด ํ™•์ธ์ด ํ•˜๋‚˜์˜ ํ‘œํ˜„์‹์—์„œ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ๋” ์ž์—ฐ์Šค๋Ÿฌ์šธ ๋•Œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‹ค. Math.max() ๋ฉ”์†Œ๋“œ๊ฐ€ ์˜ˆ๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ฒ ๋‹ค.

    when:
    def max = Math.max(1, 3)
    
    then:
    max == 3
    expect:
    Math.max(1, 3) == 3

     

    Cleanup Blocks

    given:
    def file = new File("/some/path")
    file.createNewFile()
    
    // ...
    
    cleanup:
    file.delete()

    cleanup ๋ธ”๋ก ๋’ค์—๋Š” where ๋ธ”๋ก๋งŒ ์˜ฌ ์ˆ˜ ์žˆ๊ณ , cleanup ๋ธ”๋ก์€ ๋ฐ˜๋ณต๋  ์ˆ˜ ์—†๋‹ค. ์œ„์— ์ฝ”๋“œ์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•œ ๊ฐ์ฒด๋ฅผ ํ• ๋‹น ํ•ด์ œํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ๋“ฑ์˜ ํ–‰๋™์„ ํ•œ๋‹ค. ๊ฐ์ฒด๋Š” ์‚ฌ์‹ค Garbage Collector๊ฐ€ ์ •๋ฆฌํ•ด์ฃผ๋‹ˆ๊นŒ cleanup ์—์„œ ํŠน๋ณ„ํžˆ ์‹ ๊ฒฝ์„ ์“ฐ์ง€ ์•Š(?)์•„๋„ ๊ดœ์ฐฎ์ง€๋งŒ, ํŒŒ์ผ ์‹œ์Šคํ…œ์ด๋‚˜ DB ์ปค๋„ฅ์…˜, ๋„คํŠธ์›Œํฌ ์„œ๋น„์Šค ๊ฐ™์€ ๊ฒƒ๋“ค์€ ์ž˜ ์ •๋ฆฌํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ๋ชจ๋“  feature ๋ฉ”์„œ๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ž์›์ผ ๊ฒฝ์šฐ๋Š” cleanup() Fixture ๋ฉ”์„œ๋“œ๋ฅผ ์“ฐ๋ฉด ๋œ๋‹ค.

    Where Blocks

    where ๋ธ”๋ก์€ ํ•ญ์ƒ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ์˜ค๊ณ , ๋ฐ˜๋ณต๋  ์ˆ˜ ์—†๋‹ค. data-driven ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

    import spock.lang.*
    
    class MySpockTest extends Specification {
        def "where ๋ธ”๋ก ์‚ฌ์šฉํ•˜๊ธฐ"() {
            expect:
            Math.max(a, b) == c
    
            where:
            a << [5, 3]
            b << [1, 9]
            c << [5, 9]
    
        }
    }
    

    Math.max(5, 1) == 5
    Math.max(3, 9) == 9 ์ˆœ์„œ๋Œ€๋กœ ์ด๋ ‡๊ฒŒ ํ‰๊ฐ€๊ฐ€ ๋œ ๊ฒƒ์ด๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ data tables ์„ ๋งŒ๋“ค์–ด ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

    import spock.lang.*
    
    class MySpockTest extends Specification {
        def "where ๋ธ”๋ก ์‚ฌ์šฉํ•˜๊ธฐ"() {
            expect:
            Math.max(a, b) == c
    
            where:
            a | b | c
            5 | 1 | 5
            3 | 9 | 9
        }
    }
    

    ๊ฐ’๋งŒ ๋ฐ”๊ฟ”์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์ •๋ง ํŽธํ•  ๊ฒƒ ๊ฐ™๋‹ค. Where ๋ธ”๋ก์—์„œ ํ•  ์ˆ˜ ์žˆ๋Š” Data Driven ํ…Œ์ŠคํŒ…๋„ ๋‚ด์šฉ์ด ์ •๋ง ๋งŽ์•˜๋‹ค. ์ด๊ฒƒ๋„ ์ธํ„ฐ๋ ‰์…˜๊ณผ ๋”๋ถˆ์–ด์„œ ๋” ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

    ๋งˆ๋ฌด๋ฆฌ

    ์ด๋ ‡๊ฒŒ ์–ด๋–ค ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€ํ•ด์„œ ๊ณต๋ถ€ํ•  ๋•Œ ํ•ญ์ƒ ๋ˆ„๊ตฐ๊ฐ€์˜ ๋ธ”๋กœ๊ทธ๋‚˜ ์˜์ƒ์„ ์ฐธ๊ณ ํ•˜๋ฉฐ ๊ณต๋ถ€ํ–ˆ์ง€ ์ด๋ ‡๊ฒŒ ์จ๋ณธ ์ ์ด ์ฒ˜์Œ์ด๋‹ค. ์ƒ๊ฐ๋ณด๋‹ค ์ •๋ง ๋งŽ์€ ์‹œ๊ฐ„์ด ๋“ค์–ด๊ฐ€๊ณ  ์˜์–ด๋ฅผ ์ฝ๊ณ  ์ ๋‹นํ•œ ํ•œ๊ธ€ ํ‘œํ˜„์œผ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ ์–ด๋ ค์šด์ง€ ์ƒˆ์‚ผ ๋‹ค์‹œ ๊นจ๋‹ซ๋Š”๋‹ค. ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐจ๋ถ„ํžˆ ์ฝ๋‹ค ๋ณด๋‹ˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ๋ธ”๋กœ๊ทธ๋ฅผ ๋ณผ ๋•Œ์™€ ๋‹ค๋ฅด๊ฒŒ ์ข€ ๋” ๊ผผ๊ผผํ•˜๊ฒŒ ์•Œ๊ฒŒ ๋˜๊ณ , ๋ธ”๋กœ๊ทธ์— ์“ฐ๋‹ค ๋ณด๋‹ˆ ๋˜ ์ž˜ ์ •๋ฆฌ๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

    Spock ํ”„๋ ˆ์ž„์›Œํฌ๋Š” JUnit ๊ณผ ๋น„๊ตํ•ด์„œ๋Š” ์ข€ ๋” ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ , ๋” ๋ช…์‹œ์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๋ธ”๋ก๋„ ๊ทธ๋ ‡๊ณ  ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋„ ์ •๋ง ์นœ์ ˆํ•˜๊ฒŒ ๋ณด์—ฌ์ค€๋‹ค. ์ข€ ๋” ์“ฐ๋ฉด์„œ ๊ณต๋ถ€ํ•˜๋‹ค๋ณด๋ฉด ์žฅ๋‹จ์ ๋„ ๋” ์•Œ๊ฒŒ๋˜๊ณ  JUnit ๊ณผ ๋” ๋งŽ์€ ๋น„๊ต๋ฅผ ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

    Spock ๋กœ Spring ๋„ ์–ผ๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค! 

     

     

    References

    spockframework.org/spock/docs/1.3/spock_primer.html
    spockframework.org/spock/docs/1.3/interaction_based_testing.html
    d2.naver.com/helloworld/568425

     

Designed by Tistory.