우테코 프리코스

우테코 프리코스의 assertRandomNumberInRangeTest 함수에 대하여(feat.Mockito, ByteBuddy)

exena 2025. 10. 29. 21:21

 

이 테스트 코드는 pobi, woni, 그리고 java에게 한 칸 전진을 시도하도록 한다.

해당 시도는 0~9 랜덤을 굴려서 4 이상이 나와야 한 칸 전진을 한다.

그런데 이 함수의 마지막 줄을 보면 전진, 정지, 전진이라고 랜덤 굴림의 결과를 조작하고 있다.

 

이런 야바위같은 일이 어떻게 가능한 걸까?

 

우테코 프리코스에서 제공해주는 assertRandomNumberInRangeTest 함수의 코드를 살펴보자.

이 코드를 보면 내부적으로 Randoms.pickNumberInRange 함수가 동작할 때 마다 해당 결과값을 가로채서 미리 정해둔 값들을 대신 리턴할 것이라고 예측이 가능하다.

내부적으론 Mockito 라이브러리를 사용하는데, Mock 이라는 단어 자체가 가짜라는 뜻이다. 

이 라이브러리는 테스트를 할 때 특정 함수를 실제로 실행하고 싶지는 않지만 함수 호출이 되는지는 확인하고 싶을 때 유용하게 쓰인다.

MockedStatic 객체의 when이 위에서 넘겨주는 함수를 JVM 레벨에서 치환한다. (이 방법에 대해선 마지막에 상세설명을 추가했다)

그리고 try 문이 끝나면 자동으로 mock.close()가 호출되어 치환했던 함수가 원래대로 돌아온다.

MockedStatic 객체의 인터페이스를 좀 더 자세히 보자. (내부 제너릭 구조까지 굳이 파악하고 싶다면)

thenReturn으로 첫번째 반환값인 value와 그 이후에 순차적으로 반환할 값들의 배열인 values를 넣는다.

그냥 모든 반환값을 한번에 받지 않도록 한 이유는 아마 배열 그 자체를 하나의 값으로 해석해버리는 제네릭 T 찐빠가 일어나지 않게 하기 위해서인 듯 하다. 코드의 의미를 명확하게 하기 위함도 있고.

T ... 은 varargs라고 해서 원시 타입 배열만 아니면 알아서 배열이 언박싱되어서 들어가는 듯 하다.

 

 

Mockito 간단한 설명

이 Mockito 라이브러리를 사용하지 않는다면 우리는 테스트용으로 사용하기 껄끄러운 함수(이메일 전송 등)을 테스트하기 위해서 해당 함수를 사용하는 클래스의 부모 인터페이스를 만들고 그걸 상속받는 테스트용 대체 클래스(stub 혹은 mock)를 만들어줘야 한다.

 

하지만 Mockito 라이브러리를 사용하면 아무리 함수가 많아도 자동으로 대체 클래스를 만들 수 있다.

"딸깍"

이렇게 선언하고 사용한 뒤에(스프링 부트와 조합하면 어노테이션으로 더 편하게 사용 가능하다)

verify 함수를 통해서 실제로 해당 함수가 사용된 것이 맞는지 검증하면 된다.

사용할 때 위처럼 when과 thenReturn을 사용해서 값을 치환하는 것도 가능하다.

그런데 우테코 프리코스에서 사용을 강제하는 pickNumberInRange 함수는 static 함수이기 때문에 이런 일반적인 mock으로는 치환이 불가능하다. 그래서 스태틱 함수를 치환하는 용도로 MockedStatic 함수를 사용한 것이다.

 

추가) 

자바 프로그램을 컴파일하면 이렇게 .class 확장자 파일을 생성한다.

이 파일이 일반적으로 JVM의 클래스 로더가 읽는 클래스 정보이다.

하지만 반드시 해당 파일만을 읽지는 않는다. 우리가 따로 바이트코드를 넣어줘서 클래스 정보를 생성할 수도 있다.

ByteBuddy 같은 라이브러리가 이를 지원한다.

Mockito 또한 최근 버전에서 ByteBuddy를 사용한다고 한다.

그리고 Mockito 라이브러리 공식 문서에서 설치할때 같이 넣으라고 하는 mockitoAgent 라는 자바 에이전트를 보면 단서가 잡힌다.

자바의 Instrument 패키지 설명을 보면 이 자바 에이전트에 대해 나와있다.

보면 premain이라는 함수를 정의하라고 적혀있다.

premain이라는 main 함수 동작 이전에 작동하는 함수를 만들어주자. 여기서 자바 에이전트를 리턴해야 한다.

ByteBuddy의 AgentBuilder라는 클래스를 사용해서 자바 에이전트를 만들고, installOn을 써서 적용한다.

이를 통해 메모리상에서 코드를 바꿔치기하는 것이 가능하다.

 

이러한 바이트코드 조작을 써서 디버깅 툴이나 테스트 코드의 커버리지 계산 등 여러 작업이 가능해진다.

스프링 또한 컴포넌트 스캔을 할 때 ASM이라는 라이브러리를 써서 바이트코드 조작을 수행한다.