Spock
Spock은 BDD(Behaviour-Driven Development) Framework입니다.
TDD프레임워크인 JUnit과 비슷한 점이 많으나, 기대하는 동작과 테스트 의도를 더 명확하고 간결하게 드러내주는 큰 장점이 있습니다.
해당 포스팅에서는 Spock을 사용할 때의 이점과 사용 방법에 대해 알아보겠습니다.
테스트
아래의 MemberTier라는 enum에 대한 테스트를 진행하겠습니다.
MemberTier는 각 멤버의 등급마다 다른 할인율을 적용해서 계산해주는 함수형 인터페이스를 필드로 가지고 있습니다.
public enum MemberTier {
BRONZE("할인율 0%", (price) -> {
validPrice(price);
return calPrice(price, 0);
}),
SILVER("할인율 10%", (price) -> {
validPrice(price);
return calPrice(price, 0.1);
}),
GOLD("할인율 20%", (price) -> {
validPrice(price);
return calPrice(price, 0.2);
}),
DIAMOND("할인율 30%", (price) -> {
validPrice(price);
return calPrice(price, 0.3);
});
private String description;
private Function<Long, Long> expression;
MemberTier(String description, Function<Long, Long> expression) {
this.description = description;
this.expression = expression;
}
private static void validPrice(long amount) {
if(amount < 0) {
throw new IllegalArgumentException("음수는 허용되지 않습니다.");
}
}
private static long calPrice(long amount, double discountRate) {
return (long) (amount * (1 - discountRate));
}
public long calcPrice(long amount) {
return expression.apply(amount);
}
}
이제 Enum의 각 타입이 계산을 올바르게 거치는 지 테스트 코드를 작성해보겠습니다.
Junit 테스트
@Test
public void 각_유저의_티어마다_할인율이_적용된다 () {
// given
MemberTier silver = MemberTier.SILVER;
// when & then
final long case1 = silver.calcPrice(500);
assertThat(case1, is(450L));
final long case2 = silver.calcPrice(1000);
assertThat(case2, is(900L));
final long case3 = silver.calcPrice(-500);
assertThat(case3, is(-450L));
final long case4 = silver.calcPrice(-1000);
assertThat(case4, is(-900L));
}
이때 몇 가지 문제점을 만나게됩니다.
- assertThat 메서드가 많아서 어떤 것을 써야할 지 찾아야 한다.
- is 메서드도 많아서 어떤 것을 써야할 지 찾아야 한다.
- 파라미터만 변경되고 동일한 과정이 계속되는데 과정을 재사용할 수 없다.
- 예외 throw 테스트를 할 때 해당 검증 코드가 어떤 것인지 찾기가 어렵다.
JUnit을 사용하면 위와 같은 단점들이 존재합니다. Spock은 이런 불편한 점을 해결합니다.
Spock 테스트
Spock을 사용하기 위해서는 groovy 플러그인이 추가되어야 합니다.
apply plugin: 'groovy'
추가로 spock 의존성을 추가합니다.
testCompile 'org.spockframework:spock-spring:2.1-groovy-3.0'
testCompile 'org.spockframework:spock-core:2.1-groovy-3.0'
아래는 새롭게 구성한 groovy 테스트 경로입니다.
spock 테스트는 groovy라는 언어로 작성하기 때문에 테스트 명을 문자열로 작성할 수 있습니다.
즉, 한글이나 띄어쓰기, 특수문자 등을 자유롭게 사용할 수 있습니다.
class MemberTierTest extends Specification {
def "실버 등급의 회원이 500원 품목을 구매하면 450원으로 할인이 적용된다" () {
given:
MemberTier 실버_등급 = MemberTier.SILVER;
long 금액 = 500L;
when:
long 할인_금액 = 실버_등급.calcPrice(금액);
then:
할인_금액 == 450
}
}
몇가지 예약어를 소개해드리자면
- Specification: 상속하면 Spock Test 클래스가 됩니다.
- def: groovy의 동적 타입 선언(메소드, 변수에 모두 사용. var과 유사)
- given, when, then: Spock의 feature 메소드
Spock의 feature 메소드는 setup, when, then, expect, cleanup, where 6단계의 라이프사이클을 가집니다.
아래는 생소할 수 있는 where 블록에 대한 예시입니다.
- Math.max(a, b) == c 테스트 코드의 a, b, c에 각각 5, 1, 5와 3, 9, 9가 입력된 후
- expect: 메소드가 실행됩니다.
즉, where는 feature 메소드를 파라미터화해서 실행합니다.
기존의 테스트를 Spock으로 작성하면 아래와 같이 간결하게 작성이 가능합니다.
def "실버 등급의 회원에게는 10%의 할인율이 적용된다. [금액: #price, 결과: #result]" () {
given:
def 실버_등급 = MemberTier.SILVER
expect:
실버_등급.calcPrice(price) == result
where:
price | result
500L | 450L
1000 | 900L
-500L | -450L
-1000L | -900L
}
JUnit에서 쉽지 않던 예외 검증도 Spock을 사용하면 간결하게 처리할 수 있습니다.
def "음수가 입력되면 IllegalArgumentException 발생한다" () {
given:
def 실버_등급 = MemberTier.SILVER
when:
실버_등급.calcPrice(-1)
then:
def e = thrown(IllegalArgumentException)
e.message == "음수는 허용되지 않습니다."
}
Spock의 thrown 메서드는 예외 검증 뿐만 아니라, 예외를 반환해주기 때문에 메시지를 검증하기에도 편리합니다.
마무리
Spock으로 JUnit을 대체해서 테스트를 진행해봤습니다.
감사합니다.
참고
'Server > JUnit, Spock' 카테고리의 다른 글
JUnit5 - Nested Class를 사용해서 우아하게 서비스 테스트 작성하기! (0) | 2022.09.03 |
---|---|
CQRS에서 조회 모델으로 DDL을 생성하는 이슈 해결! (0) | 2022.08.29 |
Spring Boot - Chaos Monkey를 활용한 운영 이슈 테스트! (0) | 2022.06.11 |
JUnit - TestContainers 사용하는 방법! (+ 장단점 비교) (3) | 2022.06.06 |
Mockito로 BDD 테스트 코드 작성하기 (BDDMockito) (0) | 2022.06.05 |