본문 바로가기
Development/Java

test coverage를 private constructor 혹은 메소드에 적용 방법

by 버들도령 2023. 3. 28.
728x90

test coverage를 작성중이신가요?
그런데... Class 파일의 private 생성자 혹은 메소드에 대해서도 test coverage를 적용해야할 때가 있죠?

일반적인 경우는 Class파일에서 private으로 지정된 생성자나 메소드는 호출이 불가능 합니다.
되도록이면 이런 경우의 원칙을 훼손하지 않는 것이 제일 좋겠지요.
하지만... 굳이 여기가지 검색해서 오신 것을 보면 해보고 싶으신 것 같으세요.
그래서 이런 경우에는 어떻게 해야할지에 대해서 알아보도록 하겠습니다.

예제로 아래와 같은 Class가 있다고 가정합니다.
생성자가 privte 지정자로 되어있군요.

// Example class

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

public class ResponseEntityBuilder {

	public static final String HEADER_CONTENT_TYPE = "Content-Type";
	public static final String CHARSET_UTF8 = ";charset=UTF-8";
	private static final String BODY_RTN_MSG = "rtnMsg";

	private ResponseEntityBuilder() {

		throw new UnsupportedOperationException();
	}

	public static ResponseEntity<Map<String, Object>> responseBuild(Map<String, Object> resMap) {

		HttpHeaders httpHeaders = new HttpHeaders();
		httpHeaders.set(HEADER_CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + CHARSET_UTF8);

		return new ResponseEntity<Map<String, Object>>(resMap, httpHeaders, HttpStatus.OK);
	}
}

 

아래와 같이 코드를 작성해서 테스트를 진행해 봅니다.

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
@DirtiesContext
public class ResponseEntityBuilderTest {

	@Test
	public void build() throws Exception {

		ResponseEntityBuilder responseBuilder = ResponseEntityBuilder.class.newInstance();
		assertThat(responseBuilder).isEqualTo(responseBuilder);
	}
}

 

위의 test 코드를 실행하면 다음과 같은 오류가 발생합니다.
java.lang.IllegalAccessException: Class common.utils.ResponseEntityBuilderTest can not access a member of class common.utils.ResponseEntityBuilder with modifiers "private"
호출하고자하는 class에 private 지정자는 접근할 수 없다고 나오죠?

java.lang.IllegalAccessException: Class common.utils.ResponseEntityBuilderTest can not access a member of class common.utils.ResponseEntityBuilder with modifiers "private"

	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.Class.newInstance(Class.java:436)
	at nid.license.apis.utils.ResponseEntityBuilderTest.build(ResponseEntityBuilderTest.java:72)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

Class transformation time: 0.030372065s for 11652 classes or 2.6065967215928596E-6s per class

Process finished with exit code -1

 

클래스에 대해서 getDeclaredConstructor()를 호출하여 Reflection 해줍니다.
아래와 같이 private 지정자의 생성자에 대해서 접근권한을 true로 설정해줍니다.
constructor.newInstance() 생성자를 호출합니다.
제가 테스트한 코드에서는 InvocationTargetException에 대한 처리를 추가한 것입니다.
테스트하시는 분의 코드에 맞게 사용을 하시면 되겠습니다.

이제 Coverage 테스트를 진행합니다.

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
@DirtiesContext
public class ResponseEntityBuilderTest {

	@Test
	public void build() throws Exception {

		Constructor<ResponseEntityBuilder> constructor = ResponseEntityBuilder.class.getDeclaredConstructor();
		assertTrue(Modifier.isPrivate(constructor.getModifiers()));
		constructor.setAccessible(true); // 해당 생성자에 대한 접근권한을 ture로 설정
		assertThrows(InvocationTargetException.class, () -> {
					constructor.newInstance();
		});
	}
}

 

 Test Coverage를 수행하면 다음과 같이 수행 완료된 것으로 표시 됩니다.

Test Converage 수행

 

private 메소드(Method)에 대한 JUnitTest

아래는 TestClass에 선언된 private 메소드에 대해서 Junit 테스트를 수행하도록 해주는 방법을 알아봅니다.

## 다음과 같은 클래스/메소드가 있다고 가정하고 설명합니다.

public class TestClass {
	private String makeSessionKeyByToken(String uuid) {
        return new StringBuffer("@Token|").append(uuid).toString();
    }
    .
}

 

아래와 같이 private method에 대해서 접근 권한을 허용하고, 호출/Test를 진행합니다.

        TestClass testClass = new TestClass();
        
        // 접근 권한을 수정하고자하는 메소드를 지정
        Method method = testClass.getClass().getDeclaredMethod("makeSessionKeyByToken", String.class);
        
        // 해당 메소드에 대한 접근 권한을 true로 설정
        method.setAccessible(true);
        
        // 해당 메소드를 호출하고, 결과값을 Cast해줍니다.
        String resultStr = (String)method.invoke(testClass, "TestCoverage");
        // resultStr = "@Token|TestCoverage"
        
        // org.assertj.core.api.Assertions.assertThat을 통해 실행 결과값과 기대값을 비교
        assertThat((String)method.invoke(testClass, "TestCoverage")).isEqualTo("@Token|TestCoverage");
728x90

댓글