본문 바로가기
Android/Test

Android Test - 3. Instrumented Test

by 안스 인민군 2024. 3. 31.

안드로이드 프레임워크가 연관되는 모듈은 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)

'Android > Test' 카테고리의 다른 글

Android Test - 2. Local Test  (0) 2024.03.31
Android Test - 1. Basic  (0) 2024.03.31