본문 바로가기
프로그래밍/테스트

[Spock] Spock Framework 이용하기 - 실습편

by 사바라다 2020. 8. 26.

[Spock] Spock Framework 이용하기 - 개론편
[Spock] Spock Framework 이용하기 - 실습편
[Spock] Spock Framework 이용하기 - Mock
[Spock] Spock Framework 이용하기 - Where

안녕하세요. 오늘 포스팅할 주제느 Spock Framework 이용하기 Block편입니다. Spock Framework는 Spock Framework 이용하기 - 개론편에 이어서 2번째 시간인데요. 저번 시간에는 개론에 집중했다면 오늘은 실제로 Spock Framework를 이용하여 테스트를 만들어보고 사용해보는 시간을 가지려고 합니다. 실제 테스트는 Spock Framework의 Feature Methods 요소를 이용합니다.

feature methods

feature methods는 실제 테스트 케이스를 작성하는 곳입니다. def "{이름}" () {}의 형식을 가지고 잇으며 이름에는 한글, 영문 특수문자 모두 가능합니다. Spock Framework 테스트의 전체적인 흐름(phase)은 아래와 같습니다.

  1. 테스트를 위한 셋업 ( 옵션 )
  2. 테스트 진행
  3. 결과 검증
  4. 테스트 정리 ( 옵션 )

그렇다면 spock framework를 이용하여 테스트를 한번 만들어보고 JUnit으로 만드는 것과 차이점을 보도록 하겠습니다.

def "첫 테스트"() {
  given:
  String hello = "안녕"
  def sabarada = " sabarada"

  when:
  def result = "안녕 sabarada"

  then:
  result == "안녕 sabarada"
}
  1. spock framework는 method에 public void가 아닌 def로 정의합니다. 또한 메서드 명은 ""를 이용하여 원하는 대로 지정할 수 있씁습니다.
  2. spock framework의 변수 및 함수의 마지막에는 세미콜론(;)은 필요없습니다. 있어도 되지만 생략해도 상관없습니다.
  3. spock framework는 동적타입(def)과 정적타입을 모두 지원합니다. 즉, 위 예제의 given 블록에서 hello는 정적 타입이며 sabarada는 동적타입입니다.
  4. spock framework의 assertion은 then: 블럭처럼 쉽게 처리할 수 있습니다. 좀 더 자세한 설명은 Block의 then에서 진행하도록 하겠습니다.

이렇게 간단히 본 예제에서도 Spock Framework의 편리성을 쉽게 접할 수 있습니다. 그렇다면 이제 Block의 개념과 각 Block의 종류 및 세부 내용에 대해서 알아보도록 하겠습니다.

Block

Spock Framework의 테스트에는 Block이라는 개념이 존재합니다. Block이란 feature method(테스트 메서드)애서 phase를 명시적으로 나타냅니다. phase는 우리가 일반적으로 테스트를 구성할 때 BDD라면 given, when, then과 같은 요소입니다. Spock에서는 given, when, then, expect, cleanup, where라고하는 여섯 종류의 Block이 존재합니다.

그리고 feature method는 적어도 1개 이상의 block이 구현되어야 합니다. 또한 Block은 feature method를 섹션별로 나누며 중첩될 수 없습니다. Spock Block과 각 Phase의 관계는 아래 이미지와 같습니다. 각 Block에 대해서는 아래에 자세하게 알아보도록 하겠습니다.

Block To Phase, 출처 : http://spockframework.org/spock/docs/1.3/spock_primer.html#_blocks

Given

def "given 테스트" () {
    given: //  아래 기술 내용은 given 블록임을 지정
    def stack = new Stack()
    def hi = "push me"
    String hello = "hello"
}

given:을 이용하여 given Block을 지정할 수 있습니다. given Block은 테스트가 이루어질 내용의 셋업해주는 역할을 합니다. given: Block은 선택사항이며 해당 Block을 넣는다면 어떠한 Block도 해당 Block 앞에 올 수는 없습니다. 또한 반복도 되지 않습니다.

When And Then

def "when-then 테스트" () {
    when:   // stimulus
    then:   // response
}

whenthen Block은 함께 정의합니다. whenthen Block은 각각 경우과 기대되는 결과를 정의합니다. when Block에는 어떠한 코드드 올수 있습니다. 하지만 thenBlock에는 Conditions(조건), Exception Conditions(예외 조건), 변수 정의만 올 수 있습니다. 또한 when-then 페어로 하여 여러번 올 수 있습니다.

Conditions(조건)

JUnit의 assertion과 마찬가지로 기대되는 상태값을 체크하는 것을 Spock에서는 Conditions라고 합니다. JUnit과 다른 점이 있다면 conditions는 간단한 boolean 식으로 표현됩니다. 메서드를 통해 검증하는 것이 아닌 단순한 표현식만 then: Block에 작성하면 spock framework는 그 정합성을 판단해 주는 것입니다. 그 예제는 아래와 같습니다.

def "when-then 조건 테스트" () {
    given:
    def stack = new Stack()
    def hi = "push me"
    String hello = "hello"

    when:
    stack.push(hi)

    then:
    !stack.hi
    stack.size() == 1
    stack.peek() == hi
}

만약 테스트의 conditions가 잘못됬다면 에러메시지가 출력됩니다. stack.peek() == hi 부분을 stack.peek() == hello로 변경해 보았습니다. 실제 결과를 비교하고 유사성까지 판단해주는것을 확인할 수 있습니다. JUnit에 비해 제공해주는 정보도 많으며 나름 가독성도 좋다고 생각됩니다.

stack.peek() == hello
|     |      |  |
|     push me|  hello
[push me]    false
             7 differences (0% similarity)
             (pus)h( me-)
             (---)h(ello)

Exception Condition (에외 조건)

Exception Conditions는 when Block에서 exception을 throw할 때를 가정하여 then Block에 정의합니다. thrown() 메서드를 통해서 사용되며 Exception Type을 파라미터로 사용합니다.

def "when-then Exception 테스트" () {
    given:
    def stack = new Stack()

    when:
    stack.pop()

    then:
    thrown(EmptyStackException)
    stack.empty
}

이렇게 exception 테스트를 하면 exception 내용을 알 수 없습니다. Spock Framework에서는 exception 내용을 제공하는 방법이 존재합니다. 아래와 같이 진행하면 됩니다.

def "when-then Exception 테스트" () {
    given:
    def stack = new Stack()

    when:
    stack.pop()

    then:
    EmptyStackException e = thrown()
    e.cause == null
}

Interactions

Interactions는 JUnit의 Verify#times와 동일합니다. 즉, 해당 메서드가 몇번 호출되었는지 판단할 때 Interactions를 사용할 수 있습니다. 사용하는 방법은 then Block에서 n * 를 사용하는 것입니다. 아래 예제를 보겠습니다.

def "events are published to all subscribers"() {
  given:
  def subscriber1 = Mock(Subscriber)
  def subscriber2 = Mock(Subscriber)
  def publisher = new Publisher()
  publisher.add(subscriber1)
  publisher.add(subscriber2)

  when:
  publisher.fire("event")

  then:
  1 * subscriber1.receive("event")
  1 * subscriber2.receive("event")
}

위 예제에서는 Mock을 사용하고 있습니다. JUnit에서의 Mock과 동일한 의미를 가집니다. 사용법은 다음 포스팅에서 자세히 다뤄보도록 하고 넘어가고 여기서는 Mocking된다는 것만 이해하고 넘어가도록 하겠습니다. 위 테스트를 그대로 있는 그대로 설명하면 when-then Block에서 보면 publisher#fire("event") 메서드를 실행하면 subscriber1과 subscriber2의 receive("event")가 1번씩 실행된다는 것을 테스트하고 있다는 것입니다.

Expect

expect Block은 when-then Block을 한번에 사용할 수 있는 Block입니다. when-then을 사용하는 것을 좀 더 간단하게 표현할 수 있게 됩니다. 아래와같이 사용할 수 있으며 함수의 단일 값을 판단할 때 좋습니다.

when:
def x = Math.max(1, 2)

then:
x == 2
expect:
Math.max(1, 2) == 2

CleanUp

def "cleanup 테스트"() {
    given:
    def file = new File("/some/path")
    file.createNewFile()

    // ...

    cleanup:
    file.delete()
}

cleanup Block은 Phase 4번째에 해당하는 Block입니다. 위의 예제와 같이 test가 마무리되고 정리가 필요할 때 사용할 수 있습니다. 위 예제는 파일을 생성했기 때문에 테스트 마무리 후 해당 파일을 삭제하는 작업을 cleanup Block에서 진행하고 있습니다.

Where

where Block은 항상 method에서 마지막에 옵니다. 그리고 반복하여 사용할 수 없습니다. 이 Block은 then, expect Block의 Data만을 바꿔가며 테스트 하는것을 단순화 할 수 있습니다. JUnit에서는 동일한 루틴으로 반복되는 값을 모두 //when, //then 으로 작성해 주어야합니다.

def "where 테스트"() {
    expect:
    Math.max(a, b) == c

    where:
    a << [5, 3]
    b << [1, 9]
    c << [5, 9]
}

마무리

오늘은 이렇게 Spock Framework의 Block에 대해서 자세히 실습 코드로 알아보는 시간을 가져보았습니다. Spock Framework는 제 마음에 쏙 드는 Test Framework입니다. 작성도 간단하며 가독성도 좋기 때문입니다. 다음시긴에는 Spock의 Mocking 하는 법과 Interactions에 대해서 자세히 알아보도록 하겠습니다.

오늘 내용은 80% 이상이 Spock Framework의 guide 문서를 참조하였습니다. 좀 더 자세히 궁금하신 분들은 아래 참조 url을 확인해 주세요.

감사합니다.

참조

http://spockframework.org/spock/docs/1.3/spock_primer.html#_blocks

댓글