사내 업무 중 무지 오래된(!) 프로젝트를 리팩토링하고 있었는데, 가장 어려웠던 점은 "테스트 코드가 없다는 것"이었다. 나중에 유지보수 할 때 테스트 코드가 없다면 매우 힘들어지기 때문에 테스트 코드는 필요했다.
그때 당시 조직의 철학 자체가 "코드의 문서화"를 중요시하고 있었기 때문에 그만큼 코드 리뷰할 때 가독성을 중점으로 리뷰 했었던 것 같다.
따라서 직관적으로 테스트 코드를 작성할 방법을 고민하던 중 Spock Framework에 대해 알게되었다. 이번 포스팅에서는 Spock Framework에 대해 알아보고 실제로 적용해본 과정에 대해 작성하고자 한다.
Spock Framework는 Groovy 기반의 테스트 프레임워크로, JUnit보다 훨씬 가독성 좋은 테스트 코드를 작성할 수 있게 도와준다. 팀에서 Spock Framework을 제안했을 때 Groovy 기반이라고 해서 거부감이 있었으나, Groovy 문법을 전혀 몰라도 대부분 Java 문법만으로도 충분히 테스트 코드를 작성할 수 있다.. JUnit의 차이는 다음과 같다.

추가로 Spock Framework 공식 문서에서 JUnit과 문법적으로 다른 점을 비교하였다.(아래 링크 참고)
https://spockframework.org/spock/docs/2.3/spock_primer.html#_comparison_to_junit
Spock Framework는 기본적으로 BDD(given-when-then) 기반의 테스트 포멧을 사용한다.
@Narrative('고객이 신규 카드를 발급받으면 최초로 임의의 비밀번호가 할당된다. 고객이 자신이 원하는 비밀번호로 변경할 수 있다')
class CustomerCardSpec extends Specification {
def "성공적으로 비밀번호 변경하기"() {
given: "고객이 신규카드를 발급받았다"
Card card = new Card("HDCard", "1234")
Customer customer = new Customer()
customer.addCard(card)
when: "고객이 신규카드를 이전과는 다른 비밀번호로 수정한다"
PasswordManager passwordManager = new PasswordManager()
passwordManager.changePassword(customer, "HDCard", "5678")
then: "고객의 신규카드의 비밀번호가 성공적으로 변경된다"
customer.getCard("HDCard").password == "5678"
}
}
Groovy 기반이지만, 위 코드처럼 Java 스타일로도 작성이 가능해서 문법에 대한 부담이 거의 없다. 또한 한글로 메서드 명을 작성하여 가독성이 높아졌다. Spock Framwork의 다양한 기능은 아래의 공식문서를 참조
https://spockframework.org/spock/docs/2.0/all_in_one.html
Specification을 상속받는 것이 Spock 테스트의 기본Spock Framework에서는 BDD 포멧으로 작성된 테스트 결과를 하나의 Report처럼 제공해주는 확장 기능이 존재한다. 아래의 Dependency를 추가한다.
// spock html reports
testImplementation group: 'com.athaydes', name: 'spock-reports', version: '2.0-groovy-3.0'

해당 기능은 테스트가 수행된 후에 위의 샘플처럼 그 결과가 하나의 문서로 만들어진다. 즉, BDD 형태로 테스트 케이스를 잘 작성하면 실행 결과 자체가 곧 문서 역할을 하게된다.
사내에서는 처음엔 낯선 기술이라 다들 살짝 거부감을 보였지만, 가이드 문서와 샘플 코드를 직접 작성하여 공유를 했었다. Spock에서 다양한 기능을 제공해주기 때문에 가독성이 오히려 높아졌다는 피드백이 있었고, 다들 직접 사용해보며 점점 익숙해졌고, 테스트 작성 속도도 눈에 띄게 빨라졌다.
def "장바구니에 담긴 모든 상품이 재고가 충분한지 확인한다"() {
given: "동일한 상품(tv) 2개가 존재한다"
Product tv = new Product(name: "tv", price: 1000, weight: 1000)
Product tv2 = new Product(name: "tv", price: 1000, weight: 1000)
and: "장바구니에 상품을 담고, 재고 시스템은 모의(Mock)로 구성한다"
WarehouseInventory inventory = Mock(WarehouseInventory)
Basket basket = new Basket(inventory)
basket.addProduct(tv)
basket.addProduct(tv2)
when: "장바구니에 담긴 상품들을 전부 배송할 수 있는지 확인한다"
basket.canShipCompletely()
then: "각 상품에 대해 재고 수량을 확인하고, 재고가 비었는지도 확인한다"
2 * inventory.getProductInventoryCount(_) >> 1
1 * inventory.isEmpty()
}
JUnit같은 경우에는 Mock 처리를 위해 별도의 라이브러리가 필요하지만, Spock Framework는 자체적으로 제공한다. 추가로 Mock 객체의 Stubbing, 메서드 호출 유무 등 다양한 테스트 기법을 통해 빠른 테스트 코드 작성이 가능해졌다.
Spock Framework는 단순히 테스트를 작성하는 도구를 넘어서, 테스트 코드를 "읽기 쉬운 문서"로 만들어주는 도구라고 느꼈다.
지금도 개인 프로젝트를 새로 만들 때, 테스트 코드부터 작성해야 한다면 자연스럽게 Spock을 선택하게 된다.
실제로 프로젝트를 리팩토링하고 유지보수를 진행하면서 "읽기 쉬운 테스트는 결국 유지보수에 강한 코드다"라는 걸 제대로 체감할 수 있었다.