티스토리 뷰

Java

Test#02. Groovy로 테스트하기

LichKing 2017. 8. 20. 16:39

1. Groovy

앞선 포스팅에선 jUnit으로 테스팅하는걸 소개했었다. 이번포스팅에서는 또다른 JVM언어인 Groovy로 테스트를 해보겠다. Groovy로 작성하는 이유는 가독성때문이다. 테스트 코드는 실제 프로덕션 환경에서 구동되는게 아니기때문에 성능 이슈는 비교적 신경쓸이유가 적은데, 그루비는 자바에 비해 간결한 문법(아주 별거 아니지만 기본 접근제어자가 public이기때문에 코드에 public을 적을 필요가 없다.)과 최신기술들을 많이 지원해주고있어 테스트의 가독성을 끌어올릴 수 있다.


포스팅을 하고있는 본인 역시 그루비라는 언어를 잘 안다거나 그루비로 테스트를 많이 작성해본 상태는 아니다. 블로그에 글을 씀으로서 한번 시작해보려고 하는 1인에 불과하다. 일단 한번 해보자.


dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-all', version: '1.8.4'
testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.12'
}


groovy 의존성을 추가해준다. 앞에서 작성했던 코드의 테스트를 만들어보자.


class CalculatorTest {
def calculator

@Before
void setUp(){
calculator = new Calculator()
}

@Test
void plus() throws Exception {
// when
def result = calculator.plus(2, 5)

// then
assert result == 7
}
}


이로 알 수 있는 사실들은 다음과 같다.


* 위에서도 잠깐 언급했듯 기본 접근제어자가 public이라서 접근제어자가 코드에 없다.

* 동적 타입 언어라 변수 선언시 타입을 지정하지 않는다(자바스크립트가 var 로 선언하듯 def 사용).

* 세미클론이 필요 없다.


코드가 간결해지긴 했지만 사실 원래 코드 자체가 어려운코드가 아니어서 체감하기가 힘들다. 단언문에 7이 아니라 8을 넣고 테스트를 실패시켜보자.



에러메세지가 참 아름답다. 단순히 스택 트레이스를 보여주는게 아니라 저런식으로 예쁘게 보여주기때문에 디버깅을 하는데 있어서도 수월하게 가능하다.


이번엔 앞 포스팅 예제중 Random 클래스를 익명 상속하여 구현한 테스트를 확인해보자.


<<before>>

@Test
public void plus(){
// given
RandomCalculator randomCalculator = new RandomCalculator(new Random(){
@Override
public int nextInt(){
return 3;
}
}, new Calculator());

// when
int result = randomCalculator.plus(2, 5);

// then
assertThat(result, is(10));
}


<<after>>

@Test
void plus(){
// given
def randomCalculator = new RandomCalculator([nextInt: { -> 3}] as Random, new Calculator())

// when
def result = randomCalculator.plus(2, 5)

// then
assert result == 10
}


익명 클래스를 만드는 방식도 자바보다 훨씬 간결해진걸 볼 수 있다. 람다에 익숙해져있는 상태에서 인자가 없는 경우를 저렇게 완전히 비워둔다는것을 모른채 '() -> 3' 으로 해놓고 자꾸 컴파일에러가 나서 몇분 헤맸다.


이번엔 Mockito를 이용한 테스트를 Groovy로 변경해보자. Groovy 문서를보니 언어차원에서 MockFor라는 라이브러리를 지원하는것 같다. 굿!


* Groovy Testing Guide


<<before>>

@Test
public void plus_mockito(){
// given
Random random = mock(Random.class);
when(random.nextInt()).
thenReturn(3);
RandomCalculator randomCalculator = new RandomCalculator(random, new Calculator());

// when
int result = randomCalculator.plus(2, 5);

// then
assertThat(result, is(10));
}


<<after>>

@Test
void plus_mockito(){
// given
def mock = new MockFor(Random)
mock.demand.nextInt{ 3 }
mock.use {
def calculator = new RandomCalculator(new Random(), new Calculator())

// when
def result = calculator.plus(2, 5)

// then
assert result == 10
}
mock.expect.verify()
}


이번만큼은 코드가 더 깔끔해졌다는 느낌은 들지 않지만.. 작성한 테스트를 돌려보자.



Random 클래스를 이용하고있다보니 실제 값은 돌릴때마다 달라지겠지만 여튼 테스트가 실패할 것이다. Mocking이 제대로 되지 않은것 같은데 왜일까? 잘 이해가 가지않으니 일단 Mocking 자체를 테스트해보자.


@Test
void groovy_mocking_test(){
// given
def mock = new MockFor(Random)
mock.demand.nextInt{ 5 }
mock.use {
// when
def result = new Random().nextInt()

// then
assert result == 5
}
}


이 테스트는 말끔히 성공한다. 혹시 하늘이 도와 마침 랜덤값이 5가 나온게 아닐까하여 몇번 돌려봤지만 항상 정상적으로 테스트를 성공한다. 그럼 위 테스트는 왜 안되는걸까?


위에 링크를 소개한 그루비 테스팅 가이드 문서에는 이런 얘기가 있다.

MockFor and StubFor can not be used to test statically compiled classes e.g for Java classes or Groovy classes that make use of @CompileStatic. To stub and/or mock these classes you can use Spock or one of the Java mocking libraries. 


정적 컴파일 클래스에는 사용할 수 없다고하는데 지금 이슈랑 연관이 있는건지 잘 모르겠다. 그래서 구글링을 해봤고, StackOverFlow에 비슷한 질문을 찾을 수 있었다(https://stackoverflow.com/questions/5228667/passing-mock-created-with-groovys-mockfor-as-reference).


정확히 잘 이해한건지는 모르겠지만 use 블럭내에서 생성하는 객체는 Mock 객체가 아니라 진짜 객체이며, Groovy가 진짜 객체를 호출할때 Mock 객체로 라우팅하는 작업을 하게되는데 지금과 같은 경우는 자바 클래스 내에서 호출을 하게되기때문에 Groovy가 Mock 객체로 라우팅을 하지못한다는 의미같다.(혹시 틀렸으면.. 댓글 주세요.)


스택오버플로우의 다른글을 봐보니(https://stackoverflow.com/questions/9339524/how-do-i-get-a-groovy-mockfor-stubfor-ignore-method-to-use-a-demand-method), 이런 문구가 있다.


For groovy developers I strongly recommend Spock Framework! 


마침 이번 포스팅의 마무리는 Spock으로 하려했는데! Spock에 대해 알아보자.


2. Spock

Spock 은 그루비에서 더욱 원활하고 간결한 테스팅을 지원하는 프레임워크다. 어차피 가독성을 위해 그루비를 선택했으니, 이왕이면 Spock을 사용하는 것이 좋다.


dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-all', version: '1.8.4'
testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.12'
testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
}

Spock 의존성을 추가해주자.


의존성을 추가했으면 Specification 클래스를 테스트 클래스의 상속해주자. 저 클래스가 Spock 의 핵심 클래스이다.


class SpockRandomCalculatorTest extends Specification {
}


일단 단순 Groovy 로 작성한 테스트를 Spock을 이용해서 작성해보자.


def "plus"() {
given:
def randomCalculator = new RandomCalculator([nextInt: { -> 3}] as Random, new Calculator())

when:
def result = randomCalculator.plus(2, 5)

then:
result == 10
}


!!! 단순히 의존성하나 추가한거 치고는 테스트의 모양새가 너무 많이 변했다. 특히 테스트 명칭이 문자열인거에 주목하자. 정말 저기에 아무런 문자열이 다 들어갈수 있다면 띄어쓰기나 한글도 아무문제 없을것이다.


def "더하기 테스트"() {
given:
def randomCalculator = new RandomCalculator([nextInt: { -> 3}] as Random, new Calculator())

when:
def result = randomCalculator.plus(2, 5)

then:
result == 10
}


잘 돌아간다. 물론 기존 자바에서도 얼마든지 한글 변수나 메서드명이 사용가능하고, 띄어쓰기의 경우 언더바(_)를 이용해서 작성할순 있었지만 타입 자체가 문자열인건 차원이 다른 편리함이다. 이를 통해 훨씬 더 가독성이 좋은 테스트를 작성할수 있을것이다.


또 달라진점을 하나 더 살펴보자. 기존에는 given, when, then 을 주석으로 달아놔서 구분했지만 Spock 에선 완전한 하나의 코드로 등장하고 있다. 각 블럭은 단순히 명칭만 지정하는게 아니라 각 블럭에 알맞는 기능까지 제공하고있기때문에 테스트 메서드의 패턴을 좀 더 강제화할 수 있을 것이다. 가령 예를 들면 then 블럭에서는 하나하나가 전부 단언문이기때문에 assert 같은 단언문을 적어줄 필요가 없다.


이제 Spock 으로 아까 성공하지못한 Mocking 테스트를 도전해보자.


def "랜덤 Mocking"() {
given :
def random = Mock(Random)
random.nextInt() >> 3
def randomCalculator = new RandomCalculator(random, new Calculator())

when:
def result = randomCalculator.plus(2, 5)

then:
result == 10
}


Spock 을 사용하기전보다 코드가 훨씬 간결해졌다. 이제 돌려보면 뚜둔!


Mocking of non-interface types requires a code generation library. Please put byte-buddy-1.6.4 or cglib-nodep-3.2 or higher on the class path. 


이런 메세지가 나오면서 테스트가 실패할 것이다. 아마도 Spock 의 Mocking 은 인터페이스만 지원하나보다. 역시 인터페이스 위주의 설계를...하고싶지만 지금 우린 우리가 컨트롤 할수있는 영역이 아니라 외부 라이브러리인 Random 클래스를 Mocking 하려는 것이기때문에 얘의 인터페이스를 추출할 수가 없다. 다행히도 친절한 에러메세지를 보니 라이브러리를 추가하면 뭔가가 되는것 같다.


dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-all', version: '1.8.4'
testCompile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.12'
testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
testCompile group: 'cglib', name: 'cglib', version: '3.2.5'
}


cglib 의존성을 추가해주자.

그리고 테스트를 돌려보면 드디어 녹색 바와 함께 성공하는걸 볼 수 있다!

이것으로 포스팅을 마치겠다. 개인적으로 나같은 경우 뭔가 새로운 언어나 프레임워크를 사용하기 이전에 진득히 공부를 하고나서 사용하는 편인데(그래서 뭔가 새로하나 사용하는 텀이 길다는 단점이 있다.) 그루비와 스폭은 좀 가벼운 마음으로 테스트 코드로 먼저 접근하면서 공부를 해나갈 생각이다. jUnit을 사용한 자바로 테스트를 짜든, 그루비만 쓰든, 스폭까지 쓰든 가장 중요한건 일단 테스트를 작성한다는 사실 일것이다. 2번의 포스팅으로인해 어떤 언어와 프레임워크를 사용하든 Mocking 까지 매우 간단하게 할 수 있으니 테스트를 꼼꼼히 작성하는 습관을 들이자.

예제코드는 https://github.com/LichKing-lee/groovy-test-study 에서 확인 할 수 있다.

'Java' 카테고리의 다른 글

중첩 try with resources 는 어떻게 작동할까?  (4) 2017.09.26
Utils 클래스 리팩토링  (0) 2017.08.27
Test#02. Groovy로 테스트하기  (0) 2017.08.20
Test#01. jUnit으로 테스트하기  (0) 2017.08.18
DDD#01. Domain Object  (1) 2017.07.23
다형성 연습하기  (0) 2017.07.08
공유하기 링크
TAG
댓글
댓글쓰기 폼