Spock에 대한 좋은 글이 있어서 번역해서 올립니다.
오역이 있거나 다른 의견이 있으면 댓글로 남겨주시면 감사하겠습니다.
원본 URL : https://thejavatar.com/testing-with-spock/
Spock Testing – Spock tutorial
Stubbing method calls
Spock에서는 다른 클래스나 인터페이스의 동작을 재정의할 수 있는 3 종류의 클래스를 제공하고 있습니다. : Stub, Mock, Spy
이 섹션에서는 다른 클래스를 모방만 하는 Mock과 Spy가 아닌, Stub에만 초점을 맞출 것입니다. 그렇지만 동작을 모방하는 문법은 세 클래스 모두 동일하므로 여기에 나오는 내용들 모두 Mock과 Spy에서도 동일하게 동작합니다.
Creating Stub
Stub을 생성하기 위해서는 Spock 테스트 코드 내에서 Stub () 메소드를 호출하면 됩니다.
1 2 3 4 5 6 7 8 9 10 | def "creating example stubs"() { given: List list = Stub(List) List list2 = Stub() // preffered way def list3 = Stub(List) } |
Specifying the return value
기본적으로 Stub을 사용하여 수행하려는 작업은 Stub화된 클래스의 특정 메서드가 호출 될 때 발생해야 할 작업을 정의하는 것입니다. Stub화된 메소드의 리턴 값을 지정하고 싶으면, 오른쪽 시프트 연산자 >>를 사용하여 리턴 값을 지정하면 됩니다.
1 2 3 4 5 6 7 8 9 10 | def "should return Role.USER when asked for role"() { given: List list = Stub() list.size() >> 3 expect: // let's see if this works list.size() == 3 } |
Specifying side effects
메소드 호출 결과로 특정 Side Effect를 원할 경우, 오른쪽 시프트 연산자 다음에 클로저( { } )를 넣습니다. 그렇게 하면 메소드가 테스트 중에 호출 될 때마다 클로저 내의 코드가 실행됩니다.
1 2 3 4 5 6 7 | def "specifying side effects"() { given: List list = Stub() list.size() >> { println "Size method has been invoked" } } |
Throwing an exception as the result of method invocation
위에서 사용했던 Side Effect 기법을 사용하여 Exception도 throw 할 수 있습니다.
1 2 3 4 5 6 7 | def "specifying that exception should be thrown"() { given: List list = Stub() list.size() >> { throw new IllegalStateException() } } |
Specifying different behaviour based on invocation order
호출 순서에 따라 Stub화 된 메소드의 동작을 다르게 정의 할 수 있습니다. 아래 예제에서 size () 메서드를 처음 호출 할 때는 1을 반환하고, 두 번째 호출에는 2가 반환되고 세 번째에는 3이 반환됩니다. 이때 사용하는 연산자는 오른쪽 시프트 연산자 3개가 붙은 >>>를 사용합니다.
1 2 3 4 5 6 7 | def "should return different values"() { given: List list = Stub() list.size() >>> [1, 2, 3] } |
이 문법은 사용자 정의 동작 구문과 혼합하여 사용할 수 있습니다. ( 아래 예제를 무슨 말인지 모르겠습니다.)
1 2 3 4 5 6 7 8 | def "should return different values or throw exception"() { given: List list = Stub() list.size() >> { throw new IllegalStateException() } >>> [1, 2, 3] >> { throw new IllegalStateException() } >>> [5, 6] } |
Conditional behaviour of stubbed method
우리는 몇 가지 조건에 기반하여 Stub화된 메소드에 대해 다른 동작을 지정할 수 있습니다.
다음 예제에서는 updateRoleAndReturnPreviousOne () 메서드가 Role.ADMIN 매개 변수와 함께 호출 될 때마다 Exception이 throw되고, 다른 경우에는 Role.USER가 반환되는 것입니다. 이번에는 Stub 메소드에 매개 변수가 필요하므로 메소드 호출시 "_"을 매개 변수로 사용하여, updateRoleAndReturnPreviousOne (_)을 사용했습니다.
매개 변수를 취하는 메서드는 다음 섹션에서 다룰 예정이지만이 표기법은 기본적으로 다음과 같이 사용 할 수 있습니다. updateRoleAndReturnPreviousOne () 메서드가 하나의 매개 변수로 호출 될 때마다 : 뒤 따라 오는 작업 실행 (오른쪽 시프트 연산자 이후에 있는 클로저({}) ) 또는 값 (오른쪽 시프트 연산자 이후 값)을 반환합니다.
1 2 3 4 5 6 7 8 9 10 11 12 | def "should act differently based on condition"() { given: User user = Stub() user.updateRoleAndReturnPreviousOne(_) >> { Role role -> if (Role.ADMIN == role) throw new IllegalArgumentException() else return Role.USER } } |
Stubbing methods that take parameters
Basic parameter matching
위 예제에서 제기된 문제에 대해 좀 더 자세히 알아보겠습니다.
위의 예제처럼 특정 메소드를 모방하기 위해서는 코드에서 메소드를 호출하는 방식으로 사용해야 합니다.
매개 변수를 취하는 메서드를 모방하고 싶다면 어떻게 해야 할까요? 'stubbing' 호출도 매개 변수를 전달해야 할까요? 아니면 선언을 생략해야할까요?
아래 예제를 통해 후자(선언을 생략)를 선택하면 어떻게 되는지 알아 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def "what will happen?"() { given: List list = Stub() list.get() >> 0 expect: list.get(0) == 0 } Condition not satisfied: list.get(0) == 0 | | | [] | false java.lang.Object@12baa77e |
예제에서 볼 수 있듯이, expect 섹션 내에서 발생한 호출이 given 섹션에 정의한 Stub 선언과 일치하지 않음을 알 수 있습니다. 위 예제는 given 섹션에 있는 Stub를 매개 변수가 0 인 get () 메서드가 호출 될 때마다 0을 반환하도록 수정하면 위의 예제은 정상 동작할 것입니다. ( list.get(0) >> 0 )
이런 문법은 특정 매개 변수로 메소드가 호출 될 때 리턴 되어야 하는 것을 명시적으로 지정할 수 있기 때문에 매우 편리합니다.
1 2 3 4 5 6 7 8 9 10 11 | def "should return 2 for method parameter equal to 2"() { given: List list = Stub() list.get(0) >> 0 list.get(1) >> { throw new IllegalArgumentException() } list.get(2) >> 2 expect: list.get(2) == 2 } |
Matching complex objects passed as parameters
우리는 매개변수를 받을 수 있고, Stubbing 선언에서 매개변수 검증을 할 수 있습니다. User 객체를 매개 변수로 사용하는 save () 메서드를 가진 UserService 인터페이스가 있다고 가정 해 보겠습니다. User 클래스에는 name이라는 String 속성이 있습니다.
1 2 3 4 5 6 7 8 9 | class User { String name } interface UserService { void save(User user) } |
UserService에서 save () 메서드가 호출되면 Exception이 throw되도록 하겠습니다. 좀 더 정교하게 만들기 위해 Michael이라는 이름의 사용자가 저장 될 때 Exception이 발생되도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def "should throw exception if user's name is Michael otherwise no exception should be thrown"() { given: UserService service = Stub() service.save({ User user -> 'Michael' == user.name }) >> { throw new IllegalArgumentException("We don't want you here, Micheal!") } when: User user = new User(name: 'Michael') service.save(user) then: thrown(IllegalArgumentException) when: User user2 = new User(name: 'Lucas') service.save(user2) then: notThrown(IllegalArgumentException) } |
이 예제를 실행하면 stub화된 동작이 save 메소드에 전달 된 User가 실제로 Michael이라는 이름을 가진 경우에만 실행된다는 것을 확인 할 수 있습니다. 클로저 내부의 표현식에 대한 유일한 제한은 부울 값으로만 평가되어야 한다는 것입니다.
Stub화된 메소드에 명시적인 값을 매개 변수로 지정하지 않으려면 와일드 카드를 사용하면 됩니다. Spock은 다음과 같은 와일드 카드를 제공합니다 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | given: // any argument list.contains(_) >> true // any argument of Integer type list.add(_ as Integer) >> true // any non null argument list.add(!null) >> true // any argument different than someObject list.add(!someObject) >> true |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def "should throw exception if an Integer is added to the list"() { given: List list = Stub() list.add(_ as Integer) >> { throw new IllegalArgumentException() } when: list.add(2) then: thrown(IllegalArgumentException) when: list.add("String") then: notThrown(IllegalArgumentException) } |
'IT > Spock' 카테고리의 다른 글
Spock Testing – Spock tutorial ( 번역 ) 5/5 (0) | 2017.03.28 |
---|---|
Spock Testing – Spock tutorial ( 번역 ) 4/5 (0) | 2017.03.28 |
Spock Testing – Spock tutorial ( 번역 ) 3/5 (0) | 2017.03.28 |
Spock Testing – Spock tutorial ( 번역 ) 1/5 (0) | 2017.03.28 |