Develop/Test Framework

Bill Pugh Singleton 패턴를 강제로 mock 객체로 변경하여 테스트

자라선 2020. 8. 28. 16:24
public class MailLib{

	private static class MailLibSingleton{
		private static final MailLib instance = new MailLib();
	}
    
    public static MailLib getInstance() {
		return MailLibSingleton.instance;
	}	
    
    ...
    
}

Bill Pugh Singleton 패턴은 Theard Safe와 간단하고 적은량의 코드, 이 싱글톤을 호출하기 전까지는

메모리에 객체를 생성하지 않아 실행환경에 빠르다는 장점등을 가진 싱글톤 패턴 중 하나 이다.

 

하지만 

 

    public String certify() throws Exception {
        ...

        // Singleton 객체 호출
        MailLib mailLib = MailLib.getInstance();

        // 객체의 메소드를 호출하여 함수를 실행
        // 하지만 테스트 할대 send 메소드 실행시 아무런 변화가 없었으면함
        mailLib.send(msg);

        ...
    }

이와 같이 Singleton 객체를 사용한 기능을 테스트 하고자 할때 해당 Singleton 객체를 mock으로 대체하고싶다.

하지만 Bill Pugh Singleton 패턴의 인스턴스를 가져오기 위해서는 private, final, inner class로 무장된 전역필드에 접근해야만한다..

private이라 접근도 못하는데 final까지 정의하니 아예 수정조차 못하게 되는데 

 

이것을 reflection 기술로 해결할 수는 있다.

 

    private void makeMockMailLib() throws Exception {
        //구현된 mock객체를 미리 선언
        MailLib mailLib = spy(MailLib.getInstance());

        //MailLib 하위에 있는 MailLibSingleton 클래스를 찾음
        Class<?> declaredClass = MailLib.class.getDeclaredClasses()[0];

        //MailLib의 하위의 private static class MailLibSingleton 클래스의 instance 필드를 찾음
        final Field field = declaredClass.getDeclaredField("instance");

        //private 객체에 액세스하기 위해 SecurityManager 허용 요청
        field.setAccessible(true);

        //final 객체를 수정하기 위해 리플렉션 객체를 수정함
        final Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        //MailLib.MailLibSingleton.instance의 값을 mock으로 수정
        field.set(MailLib.class, mock(MailLib.class));

        //mailLib.send 호출 시 무시한다.
        doNothing().when(mailLib).send(any(Message.class));
    }

마지막 라인으로 send를 호출시 아예 무시하도록 mock으로 대체하였다.

 

참고자료: https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection

 

※주의: 이렇게 수정된 싱글톤 객체는 이미 static 영역에 등록되어 있는 상태이다. 즉 수정된 싱글톤 객체는 모든 테스트 케이스에 적용되니 @After를 사용해 복구하는 것도 하나의 방법일 듯 하다.