ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Groovy Spock 을 쓰는 이유, 코드 작성 예시 소개
    [IT] 공부하는 개발자/Open Source 2021. 2. 15. 08:45

     

    목차

    1. Spock의 소개

    2. Spock 시작하기

    3. Spock 으로 테스트 코드 작성 예시


     

    1. Spock Introduction

    Spock의 특징

    개발에서 테스트 코드의 중요성은 아무리 강조해도 지나치지 않습니다. 훌륭한 테스트 코드 프레임워크라면, 사용자가 다양한 케이스의 검증을 구현할 수 있게 도와줌으로써, 보다 적은 힘을 들여 Code Coverage를 높일 수 있게 기여해야 합니다.

     

    Java 진영의 대표적인 테스트 코드 프레임워크인 JUnit 은 TDD 기반인 것에 비해, Spock 은 BDD 기반입니다.

    TDD 는 테스트 자체에 집중하여 개발하는 방식인 반면, BDD는 비즈니스 요구사항에 집중하여 테스트 케이스를 개발하게 됩니다.

    BDD는 전개도 사용자가 직관적으로 알 수 있도록 자연어 형식을 따르고 있습니다. 이런 식입니다.

     

    Spock 문법

    given:
    이런 테스트 세트를 주고,
    when:
    테스트를 실행하면,
    expect;
    이런 결과가 나오기를 원해.

     


    Spock의 장점

    1. 같은 테스트도 더 짧은 코드로 작성할 수 있습니다.
      • jUnit 은 퓨어 자바 코드로 작성해야 하지만 Spock 은 Groovy로 작성하기 때문입니다.
    2. jUnit 은 유닛테스트만을 위한 프레임워크인 반면 Spock 은 유닛 테스트, 통합 테스트, Mocking, Stubbing을 함께 지원합니다.
    3. 더 가독성이 좋습니다.
      • given, when, then 등의 블록으로 테스트 파트를 명시적으로 분리할 수 있기 때문입니다. jUnit으로는 주석으로 따로 표기하는 방법밖에 없습니다.

     


     

     

    2. Spock 시작하기

     

    임포트 버전은 이 포스팅을 작성하는 2021년 2월 현재까지 가장 많이 사용되는 1.1 의 2.4 버전으로 합니다.

    mvn-repository에서 Usages 탭을 참고하여 정하실 수 있습니다.

     

     

    Gradle Import

    testCompile "org.spockframework:spock-core:1.1-groovy-2.4"
    testCompile "org.spockframework:spock-spring:1.1-groovy-2.4"

    스프링으로 개발하지 않는다면 core 디펜던시만 추가해도 무방합니다.

     

     

    Maven Import

    <dependency>
      <groupId>org.spockframework</groupId>
      <artifactId>spock-core</artifactId>
      <version>1.1-groovy-2.4</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.spockframework</groupId>
        <artifactId>spock-spring</artifactId>
        <version>1.1-groovy-2.4</version>
        <scope>test</scope>
    </dependency>

     

    역시, 스프링으로 개발하지 않는다면 core 디펜던시만 추가해도 무방합니다.


     

    3. Spock으로 테스트 코드 작성 예시

    저의 개인 토이 프로젝트인 Stock Management를 불러와서 테스트 코드를 작성해 보겠습니다.

     

    3.1 Groovy Class 생성

    main 디렉토리와 같은 레벨에 존재하는 test 디렉토리에, 테스트 코드를 작성할 Groovy Class를 하나 만듭니다.

     

    Java Class 가 아니라 Groovy Class 로 구현합니다.
    원래 클래스의 레벨과 동일한 레벨에 위치시킵니다.

     


     

    3.2 Specification

    클래스는 spock.lang.Specification 을 상속하여 테스트를 구현해야 합니다.

     

    package com.stock.management.service
    
    import spock.lang.Specification
    
    class PortfolioItemPutLogService extends Specification{
    
    }

     


     

    3.3 def

    def 는 동적 타입을 선언하는 예약어입니다. 선언 후에는 어떤 타입의 객체든 주입할 수 있습니다.

    class PortfolioItemPutLogService extends Specification{
        
        def "테스트 1번"(){
            def randomNumber = 10
        }
        
    }

    def로 생성자를 구현하여 선언하면 하나의 유닛 테스트가 됩니다. "테스트 1번" 이라는 테스트를 생성하였습니다.

     

    혹은 아무 타입의 객체를 주입할 수도 있습니다. 테스트 안에서 randomNumber라는 랜덤한 타입을 선언하고 int 10을 주입했습니다.

     


     

    3.4 setup()

    테스트 클래스가 생성될 때에 setup() 메소드 안의 코드가 가장 먼저 실행됩니다.

    유닛 테스트가 실행되기 전에 선행되어야 할 코드를 작성해줍니다.

     

    class PortfolioItemPutLogServiceTest extends Specification{
    
        PortfolioItemPutLogService portfolioItemPutLogService
        def portfolioItemPutLogRepository
    
        void setup() {
            portfolioItemPutLogRepository = Mock(PortfolioItemPutLogRepository)
    
            portfolioItemPutLogService = new PortfolioItemPutLogService(
                    portfolioItemPutLogRepository
            )
        }
    
        def "테스트 1번"(){
            def randomNumber = 10
        }
    
    }

     

    portfolioItemPutLogService에서 호출되는 repository에 Mock 객체를 주입했습니다.

     


     

    3.5 given, when, then, expect, where

    given, when, then, expect, where 예약어를 사용하여 테스트를 작성합니다.

     

     

    각 예약어의 역할

    Keyword Role
    given 테스트가 실행되기 전 수행되어야 할 코드를 작성합니다. setup() 과 동일하지만, setup 은 클래스 전역에 적용되는 반면 given 은 작성된 테스트 내에만 적용됩니다.
    when 검증하고 싶은 작업 작성
    then 검증 값 입력
    expect 기대하는 수행 동작
    where 검증하고 싶은 파라미터 값을 추가하여, 조건과 결과만 바꿔가며 테스트 할 수 있다.

     

    쉬운 예시를 작성해봅니다.

     

        def "테스트 1번"() {
            given:
            def testValue = 1
    
            when:
            def returnValue = testValue + param
    
            then:
            returnValue == result
    
            where:
            param | result
            0     | 1
            10    | 11
            100   | 101
        }
    

     

    where 예약어로 테스트 케이스를 3개 작성했기 때문에, 테스트는 3번 수행됩니다.

     

     

    (선택) Gradle 사용자는 Spock 테스트에 사용할 빌드 수단을 선택할 수 있습니다. 저는 IntelliJ 내장 그래들을 사용합니다.

    참고) Run tests using 탭입니다. 

     

     

     

     

    3.6 메소드 대치 표현식

     

    유닛테스트를 작성할 예시 코드

    예시로, 위의 saveLog 메소드에 대한 테스트 코드를 작성해 보겠습니다.

     

     

        def "테스트 2번"() {
            when:
            def portfolioItem = new PortfolioItem(id: 1)
            def quality = 100
            def priceSum = 200000
            portfolioItemPutLogRepository.save(_) >> null // check!
    
    
            then:
            portfolioItemPutLogService.saveLog(portfolioItem, quality, priceSum)
    
            where:
            param | result
            0     | 1
            10    | 11
            100   | 101
        }

     

    saveLog 메소드 안에서 호출되는 portfolioItemPutLogRepository의 save의 리턴 값을 null로 대치하도록, '>>' 표현식을 대입하여 작성합니다. 이를 통해 코드에서 save 메소드가 호출되면 >> 우측의 값이 리턴됩니다.

     

     

     

    지금은 리턴 값으로 null이라는 정적 값을 지정해주었는데요, 만약 동적 값을 받아서 리턴시키고 싶다면 람다식으로 정의해서 쓰면 됩니다.

     

    위의 코드에서는 save의 인수로 어떤 입력값이 들어오던 무조건 null을 리턴시키지만, (Groovy에서 _ 표현식은 wildcard라서 그렇습니다.)

     

    portfolioItemPutLogRepository.save(_) >> {args -> args[0]}

     

    으로 작성한다면, save() 안에 0번째 인수로 들어온 PortfolioItemPutLog를 리턴시켜 줍니다.

     

     

    3.8 실행 횟수 검증

    실행 횟수도 검증이 가능합니다.

     

    유닛테스트를 작성할 예시 코드

     

    portfolioItemPutLogRepository.save 가 실행되는 횟수를 검증할 수 있습니다.

     

        def "테스트 3번"() {
            when:
            def portfolioItem = new PortfolioItem(id: 1)
            def quality = 100
            def priceSum = 200000
            1 * portfolioItemPutLogRepository.save(_) >> null
    
            then:
            portfolioItemPutLogService.saveLog(portfolioItem, quality, priceSum)
        }
    

     

     

    saveLog 가 1번 실행되어야 테스트가 통과합니다. 

    시험 삼아 실행 횟수를 0으로 바꿔보면,

    0 * portfolioItemPutLogRepository.save(_) >> null
    

     

    이렇게 테스트를 통과하지 못하고 실패합니다.

     

    혹은 최소 실행 횟수와 최대 실행 횟수를 지정하여 검증할 수도 있습니다.

     

    // 최소 1번 이상 실행
    (0.._) * portfolioItemPutLogRepository.save(_) >> null
    
    // 최대 2번까지 실행
    (_..2) * portfolioItemPutLogRepository.save(_) >> null
    
    // 최소 1번에서 최대 2번까지 실행
    (1..2) * portfolioItemPutLogRepository.save(_) >> null
    

     

     

     

    이 포스팅이 도움이 되셨다면 포스팅 하단의 하트를 눌러주세요.

     


    다른 포스팅 둘러보기

     

    2019/09/10 - [[IT] 공부하는 개발자/JAVA] - [JAVA 메모리 트러블 슈팅] 콘솔에서 JVM Heap 메모리 추적하기 : jstat, jmap

     

    [JAVA 메모리 트러블 슈팅] 콘솔에서 JVM Heap 메모리 추적하기 : jstat, jmap

    자바와 힙 메모리 관리 자바는 기본적으로 자바가상머신, JVM위에서 구동한다. 많은 프로그래밍 언어중에서 자바가 특히 편리한 이유중 하나로 JVM의 GC (Garbage collecter) 를 들 수 있다. 할당되었던

    gem1n1.tistory.com

    2019/10/28 - [[IT] 공부하는 개발자/JAVA] - [JAVA Collections API] 자료구조 요약: 구조/성능/용도

     

    [JAVA Collections API] 자료구조 요약: 구조/성능/용도

    개요 이 포스팅에서는 자바 Collections API로 표현되는 자료구조들의 성능에 대해서 이야기하고자 한다. 성능은 시간 복잡도(Time Complexity)를 기준으로 하며, 발생할 수 있는 최대 복잡도를 가리키는

    gem1n1.tistory.com

    댓글

Copyright in 2020 (And Beyond)