Testing Codes/Test 기타

[오류 해결 로그] 안드로이드 테스트 시 java.lang.RuntimeException: Method xxx in android.xxx not mocked 문제 해결법

안드로이드 메서드를 사용할 때 생기는 문제점과 원인

안드로이드 전용 클래스의 정적 메서드를 클래스 내부에서 사용한다면 테스트 시 문제가 생긴다. 예를 들어 다음과 같은 테스트 코드를 보자.

import android.text.TextUtils
import org.junit.Assert.assertTrue
import org.junit.Test

class ExampleUnitTest {
    @Test
    fun testTextIsEmpty() {
        val isEmpty = TextUtils.isEmpty("")
        assertTrue(isEmpty)
    }
}

이 테스트는 매우 간단한 테스트로, TextUtils.isEmpty를 호출해 매개 변수로 입력된 문자열이 빈칸인지 확인하기 위한 테스트이다. 하지만 이 테스트를 실행하면 다음과 같은 애러가 난다.

그림1. 애러 로그

이유는 이 테스트에서 사용한 TextUtils는 바로 android.text 패키지에 포함된 클래스이기 때문이다.

이 문제가 일어나는 이유를 이해하기 위해서는 안드로이드 API가 제공되는 방식에 대한 이해가 필요하다. 안드로이드에서 제공하는 jar에 포함된 API에는 어떠한 실제 구현체도 제공되지 않고 API만 제공한다. 즉, Context나 Bundle 같은 안드로이드 전용 클래스에 대한 구현체는 jar 파일 안에 없다. 대신 이러한 API에 대한 구현체는 바로 안드로이드 시스템 이미지에 의해 제공되기 때문에 안드로이드 기기에서 동작할 때가 되어서야 실제 구현체가 제공된다. 따라서, 테스트 실행 시에는 이러한 안드로이드 시스템 이미지가 없으므로, 예외를 던져서 테스트가 불가능해진다.

그렇다면 안드로이드의 시스템 이미지를 포함해서 테스트 하면 어떨까? 생각할 수도 있지만, 만약 실제 시스템 이미지를 포함한다면, 기기마다 시스템 이미지가 다른데 특정한 시스템 이미지에 대해 의존성이 생겨 제대로된 테스트를 진행할 수 없게 된다. 그렇다면 이런 문제를 어떻게 해결해야 할까? 먼저 정적 메서드 오류를 해결하는 방법부터 살펴보자. 

 

 

정적 메서드 오류 해결 방법

비슷한 메서드를 사용해 오류 해결

정적 메서드 오류를 해결하기 위해서는 가장 좋은 방법은 정적 메서드를 없애고 비슷한 기능을 하는 메서드로 대체하는 것이다. 예를 들어 위의 TextUtils.isEmpty는 String에서 제공하는 isEmpty 로 대체하는 것이다.

class ExampleUnitTest {
    @Test
    fun testTextIsEmpty() {
        val isEmpty = "".isEmpty()
        assertTrue(isEmpty)
    }
}

이런 방식으로 정적 메서드를 안드로이드 이미지에 영향 받지 않는 메서드로 대체하면, 테스트가 성공하는 것을 볼 수 있다.

 

그림1. 테스트 성공

 

정적 메서드를 감싸는 객체를 사용한 문제 해결

다른 방법으로는 정적 메서드를 감싸는 객체를 만들어 문제를 해결할 수 있다. 예를 들어 다음과 같이 IsEmptyCheck 인터페이스를 만든 후 그 구현체인 IsEmptyCheckerImpl을 만들면 된다.

class IsEmptyCheckerImpl(): IsEmptyChecker {
    override fun isEmpty(str: String): Boolean {
        return TextUtils.isEmpty(str)
    }
}

interface IsEmptyChecker {
    fun isEmpty(str: String): Boolean
}

이후 테스트 시에는 IsEmptyCheckerImpl을 사용하는 것이 아닌 IsEmptyChecker에 대한 Fake 객체를 만들어 정적 함수의 동작을 모방하도록 만들면 된다.

class FakeIsEmptyChecker : IsEmptyChecker {
    override fun isEmpty(str: String): Boolean {
        return str == ""
    }
}

그러면 IsEmptyChecker의 동작을 모방하는 Fake를 사용해 해당 동작을 실행할 수 있게 된다.

class ExampleUnitTest {
    @Test
    fun testTextIsEmpty() {
        val isEmptyChecker = FakeIsEmptyChecker()
        val isEmpty = isEmptyChecker.isEmpty("")
        assertTrue(isEmpty)
    }
}

즉, 정적 함수를 직접적으로 사용하는 위치를 제거함으로써 정적 함수에 대한 의존성을 없애 문제를 해결할 수 있다.

 

반응형

 

이 글의 저작권은 Dev World 에 있습니다. 글, 이미지 무단 재배포 및 변경을 금지합니다.

 

 

Kotlin, Android, Spring 사용자 오픈 카톡

오셔서 궁금한 점을 질문해보세요!
비밀번호 : kotlin22

open.kakao.com