본문 바로가기

제안&정리

[Spock] 쉽게 테스트 작성하기

회사에서 테스트를 작성하면서 객체 구조가 복잡해서 테스트 준비과정이 어려워 기존에 적용되어 있던 Junit 외에 추가로 Spock을 도입하게 되었습니다.

 

Spock은 groovy 문법으로 유연하고 가독성 높은 테스트를 작성할 수 있는 테스트 프레임워크인데요.

적용 과정과 몇가지 팁을 공유합니다.

Spring Boot 프로젝트에 Spock 적용

1. build.gradle에 의존성 추가

...
ext {
	...
    spockVersion = "2.3-groovy-4.0"
}

depedencies {
    ...
    testImplementation "org.spockframework:spock-core:${spockVersion}"
    testImplementation "org.spockframework:spock-spring:${spockVersion}"
    ...
}

2. test 폴더에 groovy 폴더 추가

java와 동일한 폴더에 spock 테스트를 만들었을 때 런타임 이슈 발생했었습니다.

잘 해결하면 같은 폴더에서 할수도 있겠지만 별도 폴더로 Junit 테스트와 별도 관리했을 때 장점도 있겠다 싶어 별도 폴더로 두었습니다.

src
   - main
   - test
       - groovy
       - java

3. 테스트 생성

package com.example.product

import spock.lang.Specification

class FooTest extends Specification {

    def "doSomthing은 true를 반화한다"() {
        given:
        def foo = new Foo()
        
        when:
        def result = foo.doSomething()

        then:
        result == true
    }
}

Spock 테스트 팁

1. given, when, then 에도 설명 추가하기

아래와 같이 given, when, then에서 설명을 추가하여 가독성을 높일 수 있습니다.

def "doSomthing은 true를 반화한다"() {
    given: "Foo 객체 준비"
    def foo = new Foo()
    foo.a = 'apple'
    foo.b = 'banana'

    when: "doSomething을 호출한다"
    def result = foo.doSomething()

    then: "true를 반환한다"
    result == true
}

 

2. and를 활용해 그룹화하기

준비구절과 검증 구절이 길어질 때가 있는데요. and를 활용해서 그룹화 할 수 있습니다.

def "doSomthing은 true를 반화한다"() {
    given: "Foo 객체 준비"
    def foo = new Foo()
    and: "Bar 객체 준비"
    def bar = new Bar()

3. where의 데이터 테이블을 활용해서 설명 높이기

데이터 테이블을 활용하면 테스트 설명문과 각 영역에서 쉽게 변수를 활용할 수 있습니다.

input 파라미터들과 구분하기 위해 expected 앞에는 |가 아닌 ||로 구분하는 것이 국룰이라고 하니 코드 작성 시 참고 해주세요:)

def "#foo와 #bar가 주어비면 #expected이다"() {
    when:
    def result = doSomething(foo, bar);

    then:
    result == expected

    where:
    foo | bar || expected
    1   | 2   || true
}

4. 반복문 안에서 검증하지 않기

팁이라기 보다는 주의사항인데요.

반복문 안에서 검증하는 경우 실패하더라도 테스트 결과에 반영되지 않습니다.

그래서 every로 처리하는 등의 다른 방법을 사용해야 합니다.

then: "foo 전체가 bar를 포함하는지 확인"
// (O)
foos.every(f -> f.conatins("bar")) 

// (X)
for(def f : foos) {
    f.conatins("bar") == true 
}

5. 객체 생성은 기본 생성자로 하고 이후 필드 설정

Spock을 사용하면서 가장 불편했던 것이 컴파일 시점이 아니라 런타임에 타입 체크를 해주는 것입니다. 

잘못된 생선자를 호출해도 런타임이 되어야만 알 수 있습니다.

유연함이 장점이기도 하지만 단점으로 작용하는 것인데요.

생성자를 선택할 때도 가능하다면 기본 생성자로 생성 후 필드를 별도로 설정해주는 것이 좋습니다.

이 외 메서드 호출 등에서도 언어적인 특성을 고려해서 테스트를 작성하는 것이 좋습니다.