Spock Framework


1. Spock Framework?

Spock FrameworkGroovy 언어에서 동작하는 명세 프레임워크로 BDD를 편하게할 수 있도록 도와준다. SpockJavaGroovy 어플리케이션을 위한 명세 프레임워크로 Groovy(DSL)로 작성하므로 간결하고 직관적인 장점이 있다. 또한, 기존의 JavaJUnit, Hamcrest, Mockito를 전부 다 학습하는 것보다 손쉽게 학습할 수 있고, Mock, Stub, Spy등 사용이 편리하고 명세를 작성하기 편리하다. JavaGroovy 어플리케이션을 위한 프레임워크이므로 Java환경에서도 사용할 수 있다.

2. Lifecycle

  • setup: 메소드 실행 전에 실행(given)

  • when: 행위에 대한 명세를 작성

  • then: 행위에 대한 예측을 작성

  • expect: 행위에 대한 명세와 예측을 작성(when + then)

  • cleanup: 메소드 실행 후에 실행

  • where: 여러 값에 대해 반복행위를 할 때 작성

spock lifecycle
Figure 1. Spock Lifecycle

3. Example

앞서 BDD 포스트에서 작성한 예시Spock으로 변환하게 되면 아래와 같다.

package kr.pe.nuti.home.api.service.todo

import kr.pe.nuti.home.api.domain.todo.TodoItem
import kr.pe.nuti.home.api.enumeration.todo.TodoState
import kr.pe.nuti.home.api.exception.todo.IllegalStateChangeException
import kr.pe.nuti.home.api.repository.todo.TodoItemRepository
import spock.lang.Issue
import spock.lang.Narrative
import spock.lang.See
import spock.lang.Specification
import spock.lang.Title

@Title("Todo Item의 상태를 변경한다.")
@Narrative("""
Todo Management System을 사용하는 사용자가
Todo Item의 상태관리를 위해서
각각의 Todo Item의 상태를 변경할 수 있다.
상태 변경은 Todo > Doing, Doing > Done,
Done > Doing, Doing > Todo로만 할 수 있다.
""")
class TodoServiceStateChangeSpec extends Specification {

  TodoService service
  def todoItemRepository

  def setup() {
    todoItemRepository = Mock(TodoItemRepository)
    service = Spy(TodoService)
    service.todoItemRepository = todoItemRepository
  }

  @See(["https://github.com/hyeonil/smart-home-api/issues/6"])
  @Issue("#6")
  def "Todo상태를 Doing상태로 변경하면 상태가 변경된다."() {
    given: "Todo 상태의 Todo Item"
    TodoItem savedItem = new TodoItem([idx: 1L, state: TodoState.TODO])
    TodoItem changedItem = new TodoItem([idx: 1L, state: TodoState.DOING])
    todoItemRepository.findById(_) >> Optional.of(savedItem)
    todoItemRepository.save(_) >>  changedItem

    TodoItem item = new TodoItem([idx: 1L])

    when: "Todo Item의 상태를 Doing으로 변경한다."
    def result = service.changeState(item, TodoState.DOING)

    then: "Todo Item의 상태가 Doing으로 변경된다."
    1 * service.getItem(_)
    result.state == TodoState.DOING
  }

  @See(["https://github.com/hyeonil/smart-home-api/issues/6"])
  @Issue("#6")
  def "Todo상태를 Done상태로 변경하면 상태가 변경되지 않고 예외사항이 발생한다."() {
    given: "Todo 상태의 Todo Item"
    TodoItem savedItem = new TodoItem([idx: 1L, state: TodoState.TODO])
    todoItemRepository.findById(_) >> Optional.of(savedItem)

    TodoItem item = new TodoItem([idx: 1L])

    when: "Todo Item의 상태를 Done으로 변경한다."
    service.changeState(item, TodoState.DONE)

    then: "Todo Item의 상태가 변경되지 않고 예외사항이 발생한다."
    1 * service.getItem(_)
    thrown(IllegalStateChangeException)
  }
}