Android/Test

Android Test - 3. Instrumented Test

안스 인민군 2024. 3. 31. 21:22

안드로이드 프레임워크가 연관되는 모듈은 jvm 위에서는 실행할 수가 없다.

따라서 테스트를 에뮬레이터나 실기기 위에서 수행해야 되는데 이것을 인스트루먼티드 유닛 테스트라고 합니다

여기서는 예제로 메인 액티비티를 생성한 다음에 그 라이프사이클 상태를 확인하는 인스트루먼티드 테스트를 해보겠습니다

우선은 테스트에 필요한 Dependency를 추가해줍니다

androidTestImplementation "androidx.test:core:1.5.0"
    androidTestImplementation "androidx.test.ext:trush:1.5.0"
    androidTestImplementation "androidx.test:rules:1.5.0"

다음은 MainActivity 클래스로부터 테스트를 작성해 보겠습니다. 이름은 MainActivityTest로 하고 테스트 라이브러리는 JUnit4로 하겠습니다. 그리고 안드로이드 SDK를 사용해야 되기 때문에 파일은 android-test 디렉토리에 저장하도록 하겠습니다

테스트를 실행할 런너를 runwith로 지정을 해주는데 여기서는 android-junit4로 지정하겠습니다.

그러면은 코드를 써보도록 하겠습니다

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@SmallTest
class MainActivityTest {

//    private lateinit var activityScenario: ActivityScenario<MainActivity>
//    @Before
//    fun setUp() {
//        activityScenario = ActivityScenario.launch(MainActivity::class.java)
//    }
//
//    @After
//    fun tearDown() {
//        activityScenario.close()
//    }

    // 위 처럼 생성하고 닫는 작업을 해주는 것을 Rule을 사용하면 편하게 사용이 가능하다.
    @get:Rule
    var activityScenarioRule: ActivityScenarioRule<MainActivity> =
        ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun test_Activity_State() {
        // 테스트를 위해 액티비티를 생성해야되는데 activityScenarioRule 사용하면 액티비티를 손쉽게 생성이 가능하다.
        val activityState = activityScenarioRule.scenario.state.name
        assertThat(activityState).isEqualTo("RESUMED")
    }
}

 

이렇게 간단하게 테스트 코드를 작성하였다.

그러나 위 처럼 할 시 에뮬레이터나 기기 위에서 실행되야 되기 때문에 보시는 것처럼 속도가 느립니다.

이 문제를 개선하기 위해서 개발된 것이 로봇 레트릭입니다.

로봇 레트릭은 Shadow라는 이름으로 안드로이드 SDK의 테스트 더블을 구현을 해주기 때문에 안드로이드 프레임워크를 사용해야 되는 테스트를 JVM 위에서 실행을 할 수가 있습니다. 로보렛트릭을 사용해서 인스트루먼티드 테스트를 수행해 보도록 하겠습니다.

우선은 빌드 그래들에 Dependency를 추가해 줍니다.

    android {
        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    }

dependencies {
	//...
    testImplementation 'org.robolectric:robolectric:4.6.1'
}

이후 로보 레트릭이 적용된 테스트는 Java Virtual Machine 위에서 돌아가는 로컬유닛 테스트가 되기 때문에 테스트 파일을 unit-test 디렉토리에 저장을 해줍니다.

테스트 런너는 robo-retric-test-runner로 지정을 해줍니다.

그러면 아래와 같이 애뮬레이터를 띄우지 않고도 실행이 가능하다.

@RunWith(RobolectricTestRunner::class)
class MainActivityTest {

    @Test
    @SmallTest
    fun test_Activity_State() {
        val controller = Robolectric.setupActivity(MainActivity::class.java)
        val activityState = controller.lifecycle.currentState.name
        assertThat(activityState).isEqualTo("RESUMED")
    }
}

그런데 중요한 점은 Robolectric이 4.0으로 버전업 되면서 AndroidX 패키지에 통합이 됐다.

따라서 앞에서 사용한 SetupActivity 같은 3.0까지의 API는 더 이상 사용되지 않고 제거된다.

AndroidX 테스트 API를 사용하면 러블렛트릭 테스트를 사용하든 계측 테스트를 작성하든 상관없이 동일한 안드로이드 개념에 대해 학습할 수 있는 단 하나의 API 세트로 개발자의 인지 부하를 줄일 수 있다.

그림을 보시면 이렇게 로컬 테스트랑 인스트루먼티드 테스트에서 동일한 테스트 코드를 만들고 런너를 JUnit4 런너로 지정을 하면은 이 런너가 알아서 계측 테스트인지 로컬 테스트인지 판단을 한 다음에 계측 테스트라면은 알아서 로봇 레트릭 런너를 적용을 해서 로컬 테스트로 수행을 하고 로컬 테스트는 또 로컬 테스트로 수행을 해주고 하는 식으로 안드로이드 Junit4 Runner가 자동으로 전환을 해주게 된다.

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner

//@RunWith(RobolectricTestRunner::class)
//class MainActivityTest {
//
//    @Test
//    @SmallTest
//    fun test_Activity_State() {
//        val controller = Robolectric.setupActivity(MainActivity::class.java)
//        val activityState = controller.lifecycle.currentState.name
//        assertThat(activityState).isEqualTo("RESUMED")
//    }
//}

@RunWith(AndroidJUnit4::class)
@SmallTest
class MainActivityTest {

    @get:Rule
    var activityScenarioRule: ActivityScenarioRule<MainActivity> =
        ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun test_Activity_State() {
        val activityState = activityScenarioRule.scenario.state.name
        assertThat(activityState).isEqualTo("RESUMED")
    }
}

 


참고

- 냉동코더의 알기 쉬운 Modern Android Development 입문 (Link)