Spock 사용 시 주의해야 할 것들
작성일: 2020-11-23 23:27
# where block에는 @Shared, static
class SpockTest extends Specification {
@Shared int sharedMin = 1
@Shared int sharedMax = 2
static int MIN = 1
static int MAX = 2
def getMaxValue() { return 2 }
def getMinValue() { return 1 }
int minValue = 1
int maxValue = 2
def "max"() {
expect:
Math.max(a, b) == max
where:
a | b || max
1 | 2 || 2
sharedMin | sharedMax || sharedMax
MIN | MAX || MAX
getMinValue() | getMaxValue() || getMaxValue()
// minValue | maxValue || maxValue 불가능
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- where block에서 공유 필드 데이터를 사용하기 위해선 @Shared or static 변수만 사용할 수 있습니다.
- 리터럴 or method 호출은 가능합니다.
# helper method에는 def가 아닌 void
class SpockHelperMethodTest extends Specification {
class Point {
int x
int y
Point(int x, int y) {
this.x = x
this.y = y
}
}
def "point"() {
expect:
// spock은 def 메서드의 내부의 assert를 파악할 수 없다.
// assertPointWithDef(new Point(1, 2), new Point(1, 2))
assertPointWithVoid(new Point(1, 2), new Point(1, 2))
}
def assertPointWithDef(Point actual, Point expect) {
assert actual.x == expect.x
assert actual.y == expect.y
}
void assertPointWithVoid(Point actual, Point expect) {
assert actual.x == expect.x
assert actual.y == expect.y
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 테스트를 작성할 때 중복을 제거하기 위해 helper method를 이용할 때가 있는데 spock에서 then or expect block에서 helper method를 사용할 땐 void method를 사용해줘야 합니다.
- def 메서드의 경우 spock은 해당 메서드의 return 값이 true인지를 판별하게 됩니다.
- assertPointWithDef는 null을 반환하게 되므로 expect block은 테스트가 실패했다고 보고합니다.
- void 메서드를 명시적으로 표현해줘야만 spock은 해당 메서드 내부의 assert문을 각각 수행해줍니다.
# mocking에서 사용되는 변수의 위치를 주의하자
@RequiredArgsConstructor
class MessageService {
private final MessageSender messageSender;
void sendMessage(String message) {
messageSender.send(message);
}
}
public interface MessageSender {
void send(String message);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- MessageService에서 MessageSender를 통해 message를 send하는 로직이 있다고 가정해보겠습니다.
# MissingPropertyException 예외 발생
class MockingTest extends Specification {
def "send message"() {
given:
MessageSender messageSender = Mock()
MessageService messageService = new MessageService(messageSender)
when:
messageService.sendMessage("message")
then:
def message = "message"
1 * messageSender.send(message) // mocking
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 코드가 깔끔하진 않지만 에러가 전혀 발생할거 같지 않은 코드입니다. 하지만 테스트를 실행하면 MissingPropertyException가 발생합니다.
- 그 이유는 spock이 구문을 분석할 때 then block에 존재하는 mocking 구문을 파악한 후 when when block 앞으로 이동시키기 때문입니다.
- 즉 아래와 같이 코드가 구성될 것입니다.
class MockingTest extends Specification {
def "send message"() {
given:
MessageSender messageSender = Mock()
MessageService messageService = new MessageService(messageSender)
// 실제 코드 배치는 이렇게 될 것이다.
1 * messageSender.send(message)
when:
messageService.sendMessage("message")
then:
def message = "message"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 이로 인해 message라는 변수는 실제 런타임 시 mocking 구문에서 찾을 수 없게 되어 에러가 발생하게 됩니다.
# 해결책 1) interaction blcok 활용
class MockingTest extends Specification {
def "send message"() {
given:
MessageSender messageSender = Mock()
MessageService messageService = new MessageService(messageSender)
when:
messageService.sendMessage("message")
then:
interaction { // 해당 blcok을 활용하면 내부 코드가 함께 이동된다.
def message = "message"
1 * messageSender.send(message)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- interaction block 내부의 구문들은 실행 시 함께 when block 앞으로 이동되어 에러가 발생하지 않습니다.
# 해결책 2) given or where block에 변수를 명시하자
class MockingTest extends Specification {
def "send message"() {
given:
MessageSender messageSender = Mock()
MessageService messageService = new MessageService(messageSender)
def message = "message" // given block에 변수 지
when:
messageService.sendMessage("message")
then:
1 * messageSender.send(message)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- mocking 구문은 when 앞으로 이동되기 때문에 given block에 변수를 명시하면 에러가 발생하지 않습니다.
# stubbing과 mocking은 동시에 명시하자
# mockito style로 테스트 작성 시 에러 발생
class MockingTest extends Specification {
def "send message"() {
given:
MessageSender messageSender = Mock()
MessageService messageService = new MessageService(messageSender)
messageSender.send("message") >> { throw new RuntimeException() } // stubbing
when:
messageService.sendMessage("message")
then:
1 * messageSender.send("message") // mocking
thrown(RuntimeException)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 흔히 mockio에서 테스트 코드를 작성할 때처럼 given 절에 stubbing, then 절에 mocking 관련 코드를 넣게 되면 spock에선 테스트가 실패합니다.
- 그 이유는 mocking 구문을 when block 앞으로 이동시킬 때 동일한 대상으로 지정된 stubbing 구문은 정상 적용되지 않기 때문입니다.
- 즉 RuntimeException을 던지는 stubbing은 적용되지 않아 테스트가 실패합니다.
- 아래와 같이 stubbing, mocking을 동시에 명시하면 문제는 해결됩니다.
class MockingTest extends Specification {
def "send message"() {
given:
MessageSender messageSender = Mock()
MessageService messageService = new MessageService(messageSender)
when:
messageService.sendMessage("message")
then: "stubbing, mocking은 동시에 명시하자"
1 * messageSender.send("message") >> { throw new RuntimeException() }
thrown(RuntimeException)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14