본문 바로가기
이론/Spring

[Spring Test] private로 선언된 메소드를 어떻게 테스트 할 수 있을까

by 6161990 2022. 1. 23.

Today’s Study Topic

private로 선언된 메소드를 어떻게 테스트 할 수 있을까.

업무를 하다가 private으로 되어있는 필드에 직접 접근해야하는 상황이 있었다.

당시에는 어쩔 수 없이 바로 public으로 변경했지만,, 찝찝했다. 

 

private로 선언된 메소드 또는 필드를 테스트하기 위해 public 으로 변경하는 것이 과연 맞을까?

일반적으로 테스트를 위해서 production 코드의 접근 범위를 넓히는 것이 허용되는 걸까?

클래스의 노출 범위가 커지게 되는 건데,, 그럼 private로 선언된 메소드나 필드를 어떻게 테스트 하지?

 

그로부터 한달이 지났나. 잊을 듯 말 듯한 시기에 관련 팁을 하나 얻었다. 

Spring Test Reference를 읽다가 private 필드, 메소드에 접근해 테스트 할 수 있는 방법을 알게되었다. 

답은 레퍼런스에 다 있다더니.. 정말인건가?

 

방법은 크게 두 가지였다. 

1. 스프링 테스트 유틸

2. 자바 lang 에서 제공하는 기능

 

테스트를 위해  객체를 하나 예로 만들어보았다.

 

📍 FoodIngredients 객체 

레시피 속 재료 정보를 담는 객체다.

kcal 와 kcal 값을 세팅해주는 메소드는 protected 로 지정했고, test는 다른 모듈에서 진행해보았다.

결국 다른 패키지니까 private 에 접근하려는 경우와 동일하다. 

 

방법 1. ReflectionTestUtils  : org.springframework.test.util.ReflectionTestUtils

 

ReflectionTestUtils is a collection of reflection-based utility methods. You can use these methods in testing scenarios where you need to change the value of a constant, set a non-public field, invoke a non-public setter method, or invoke a non-public configuration or lifecycle callback method when testing application code for use cases such as the following:

 

  • private한 필드를 ReflectionTestUtils에서 제공하는 setField() 로 데이터를 넣어주었다. 
ReflectionTestUtils.setField({인스턴스}, {접근하려는 private 필드명}, {넣어줄 데이터 값});
  • private한 메소드에 접근하기 위해 ReflectionTestUtils에서 제공하는 invokeMethod() 로 메소드를 수행했다. 
ReflectionTestUtils.invokeMethod({인스턴스}, {호출하려는 private 메소드명}, {인자1}, {인자 2}, ...);

 

방법 2. Field : java.lang.reflect.Field

  • java.lang.reflect.Field에서 제공하는 getDeclaredField() 로 변수를 세팅했다. 
Field kcal = 접근하려는 객체.class.getDeclaredField({접근하려는 필드명});
kcal.setAccessible(true);
kcal.set({인스턴스}, 데이터 값);

 

+) PrivilegedAccessor ; 접근 할 수 있는 특혜(?)를 주는 라이브러리 

들어가서 사용 예를 확인해보면 다소 복잡할 수 있는 java.lang.reflect 를 편리하게 이용할 수 있다. 

 

Privileged Accessor

PrivilegedAccessor is a simple to use Java framework for accessing private methods, attributes, and constructors via reflection. Introduction In unit-testing you often need to prepare and later inspect the unit-under-test before you can test it. For instan

sebastiandietrich.github.io

 

 

 

📍 Reflection의 단점

Reflection을 이용하면 강제적으로 private 메소드를 호출할 수 있다. 다만 이렇게 하면 접근하려는 메소드,필드명을 String값으로 넘겨지게 되므로, compile time에 메소드명의 오타가 검증되지 못하고, refactoring으로 메소드명을 바꾸어도 자동으로 String으로 적힌 부분은 바뀌지 않는 단점이 있다. 부작용을 감수하고서라도 쓰겠다고 각오가 된 곳에 제한적으로 사용하는 것을 권장한단다. 

 

 

📍관련 자료를 서치하다가 찾은 또 다른 방법 @VisibleForTesting

하지만, 해당 어노테이션은 테스트를 위한 protected 메소드임을 명시적으로 해놓은 것이지, 외부에서 접근하려면 접근할 수 있다..

어쨌든 protected이기 때문에. 어떤 기능을 하는 어노테이션이 아니라 문서화에 그친다는 거다..!

 

 

Relevant Question

📌 QUESTION : private 필드, 메소드에 대한 테스트가 필요할까? 접근 제한자 변경은 언제 일어나는걸까?

많은 경우에 private method는 public 메소드에서 extract method 되어서 나온 거니까, public을 통해서 간접적으로 테스트를 하는 것이 자연스러워 보인다. 근데 private 영역만 따로 테스트를 해야지 더욱 다양한 테스트 케이스를 작성할 수 있다면 ?  그러면 차라리 필드를 public으로 전환하라는 신호 아닐까? 설계를 바꾸거나 ..이건 아닌가? 왜냐면 private 메소드가 하는 일이 큰거니까. 별도의 클래스로 분리하거나 하위 클래스에서 상속을 해서 대체할 수 있는 가능성을 고려해서 protected로 해두는 것도 생각해봐야하는 케이스도 있을 것 같다. 그렇다면 ReflectionTestUtils 등을 이용하는 것보다 public으로 그냥 바로 바꿨던 게 맞았던 것일수도 있지. (테스트를 위해서만이 아니라 필드나 메소드의 역할이 크니까..)

 

→ private 또는 protected 였던 메소드를 언제 public 으로 전환하냐. 또 어떤 이유가 있어야하는걸까. 어떤 기준이 있어야하는걸까.

→ public으로 노출 한다는 것의 의미는 뭘까. 누군가가 사용한다는 말이다. 협력할 누군가가 있다는 말이다. 

→ private로 감춘다는 의미는 뭘까. 직접적 협력을 하지 않는 다는 말일수도 있을 것 같다. 이 데이터를 수정한다는 것은 직접적 협력을 하고 있는 다른 무언가(아마 메소드)의 협력을 깨뜨린다는 의미다. 그래서 private을 접근할 수 있는 방법이 있어도 그건 위험한 일이라고 다들 말한다. private한 무언가를 테스트 해야되는 상황이 온다면 역할과 책임 설정을 처음부터 잘못했거나 테스트 위치가 잘못되었음을 입증하는 순간이기도 할 것이다. 

 

 

Relevant Reference