ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spock 로 테스트 해보기
    🌱 spring 2022. 10. 6. 13:53

    Spock ❓❓❓


    Spock 는 BDD(Behaviour-Driven Development) 프레임워크 이다.

    • Java 및 Groovy를 위한 테스트 프레임워크
    • 내부적으로 JUnit Runner로 구동되며, 대부분의 IDE에서 지원된다.

    TDD 프레임워크인 JUnit 과 비슷한 점이 많지만, 기대하는 동작과 테스트의 의도를 더 명확하게 드러내주고 산만한 코드는 뒤로 숨겨주는 등 큰 장점이 있다.

    기존 JUnit의 단점

    • 테스트시 중복 코드가 많다.
    • JUnit, Hamcrest, Mockito를 알아야 코드를 이해할 수 있다.

    Spock 시작하기


    • spring-boot : 2.7.4
    • java : 11

    build.gradle

    testImplementation'org.spockframework:spock-core:2.3-groovy-4.0'
    testImplementation'org.spockframework:spock-spring:2.3-groovy-4.0'
    

    이렇게 의존성을 추가해주고

    groovy 파일을 생성 후 Specification 클래스를 상속 받으면 spock을 사용할 수 있다.

    ‘소수점 버림’ 테스트 해보기

    CalculateTest.java

    public class CalculateTest {
        public static long calculate(long amount , float rate, RoundingMode roundingMode) {
            return BigDecimal.valueOf(amount * rate * 0.01)
                    .setScale(0, roundingMode ).longValue();
        }
    }
    

    SpockTest.groovy - 1

    class SpockTest extends Specification {
    
        def "금액의 퍼센트 계산 결과 값의 소수점 버림을 검증"() {
    
            //given
            RoundingMode 소수점버림 = RoundingMode.DOWN;
    
            //when
            def calculate = CalculateTest.calcuate(10000L, 0.1f, 소수점버림)
    
            //then
            calculate == 10L
        }
    
    }
    

    💣 Error 발생

    • 코드 블록을 선언하지 않은 것이 실패 원인

    Block (코드 블록)

    Spock에서는 given, when, then 과 같은 코드 블록 을 block이라고 부른다.

    block은 테스트 메서드 (feature method) 내 최소한 하나는 있어야 한다.

    JUnit에서는 있어도 되고 없어도 되는데 Spock에서는 필수이다.

    • JUint에서는 주석으로 하지만, spock에서는 주석처리를 하지 않음
    given( or setup) JUnit의 Given 처럼 테스트에 필요한 환경을 설정하는 작업
    - 항상 다른 블록보다 상위에 위치
    when 테스트 코드를 실행
    then 테스트 코드 결과 검증, 예외 및 조건에 대한 결과 확인 작성한 코드
    - 한줄이 assert 문
    expect 테스트 할 코드 실행 및 검증 (when + then)

     

    SpockTest.groovy - 2

    class SpockTest extends Specification {
    
        def "금액의 퍼센트 계산 결과 값의 소수점 버림을 검증"() {
    
            given:
            RoundingMode 소수점버림 = RoundingMode.DOWN;
    
            when:
            def calculate = CalculateTest.calcuate(10000L, 0.1f, 소수점버림)
    
            then:
            calculate == 10L
        }
    
    }
    

    잘 수행된다!

    Where blocks

    테스트를 하면서 다양한 케이스를 검증할 때가 있다.

    • where block 을 사용하면 간단하게 해결할 수 있다.
    def "여러 금액의 퍼센트 계산 결과값의 소수점 버림을 검증"() {
      given:
      RoundingMode 소수점버림 = RoundingMode.DOWN;
    
      expect:
      CalculateTest.calcuate(amount, rate, 소수점버림) == result
    
      where:
      amount | rate  | result
      10000L | 0.1f  | 10L
      2799L  | 0.2f  | 5L
      159L   | 0.15f | 0L
      2299L  | 0.15f | 3L
    }
    

    테스트가 실패했을 경우 JUnit은 제일 먼저 실패한 케이스만 알 수 있다.

    하지만, Spock는 실패한 모든 테스트 케이스와 그 내용을 더 자세히 알 수 있다.

    예외 케이스

    0보다 작은 음수가 들어왔을 때 → “예외” 상황

    def "음수가 들어오면 예외가 발생하는지 확인"() {
       given:
       RoundingMode 소수점버림 = RoundingMode.DOWN;
    
       when:
       CalculateTest.calcuate(-10000L, 0.1f, 소수점버림)
            
       then:
       def e = thrown(NegativeNumberNotAllowException.class)
       e.message == "음수는 계산할 수 없다."
    
    }
    

    Spock에서 예외는 thrown() 메서드로 검증이 가능하다.

    • thrown() 메서드는 발생한 예외를 확인할 수 있을 뿐 아니라 객체를 반환해
    • 예외에 따른 메시지도 검증을 할 수 있다.

    Mock 테스트

    Spock에서 Mock 테스트도 간단하다.

    def "주문 금액의 소수점 버림을 검증"() {
        given:
        RoundingMode 소수점버림 = RoundingMode.DOWN;
        def orderSheet = Mock(OrderSheet.class)
    
        when:
        long amount = orderSheet.getTotalOrderAmount()
    
        then:
        orderSheet.getTotalOrderAmount() >> 10000L
        10L == CalculateTest.calcuate(amount, 0.1f, 소수점버림)
    
    }
    

    가짜 객체의 반환 값은 ‘>>’으로 설정할 수 있고 예외를 발생시키고 싶다면 아래와 같이 하면 된다.

    orderSheet.getTotalOrderAmount() >> 
    

    Bean의 Mocking

    Spock 를 쓰더라도 Spring의 Bean을 mocking 하려면 Mockito 방식을 사용해야 한다.

    @MockBean
    OrderRepository orderRepository
    
    def "주문 금액 소수점 버림 조회" () {
      given:
      given(orderRepository.findById(anyLong()))
           .willReturn(Optional.of(new Order(10000L, 0.1f)))
    
      when:
      Order order = orderService.getOrder(1)
            
      then:
      order.getPrice() == "10L"
            
    }
    
    • 하지만 이 경우 groovy 언어 특성상 Overloading Method를 mocking할 때 어떤 메서드를 Mocking 해야 할지 선택하지 못해 Mocking 실패로 테스트가 실패하게 된다.

    그럴 경우 Spock에서 제공하는 DetachedMockFactory 를 통한 Mock 생성을 통해 bean에 대한 mock 생성이 가능하다.

    • 하지만, 이 경우에도 JpaRepository인 경우 mocking하지 못한다.
    • JpaRepository 인터페이스 mocking이 필요할 경우 JUnit 또는 Spock에서 Mockito를 사용해야 한다.

    참고 자료

    https://dev.gmarket.com/37

    https://meetup.toast.com/posts/268

    https://techblog.woowahan.com/2560/

    https://goodteacher.tistory.com/336

    '🌱 spring' 카테고리의 다른 글

    JPA - N + 1 문제  (0) 2022.10.11
    영속성 컨텍스트  (0) 2022.10.11
    JPA와 Hibernate, Spring data JPA  (0) 2022.10.10
    MVCC  (1) 2022.10.05
    QueryDSL  (1) 2022.10.05

    댓글

Designed by Tistory.