앱 활동 테스트

활동은 앱 내 모든 사용자 상호작용의 컨테이너 역할을 하므로 다음과 같은 기기 수준 이벤트 중에 앱 활동이 어떻게 동작하는지 테스트하는 것이 중요합니다.

  • 기기의 스마트폰 앱과 같은 다른 앱이 앱의 활동을 중단시킵니다.
  • 시스템이 활동을 제거하고 다시 생성합니다.
  • 사용자가 PIP (picture-in-picture) 또는 멀티 윈도우와 같은 새로운 윈도잉 환경에 활동을 배치합니다.

특히 활동이 활동 수명 주기에 설명된 이벤트에 응답하여 올바르게 동작하는지 확인하는 것이 중요합니다.

이 가이드에서는 앱의 활동이 수명 주기의 여러 상태를 통해 전환될 때 앱이 데이터 무결성과 우수한 사용자 환경을 유지하는 능력을 평가하는 방법을 설명합니다.

활동 상태 변경

앱 활동 테스트의 한 가지 주요 측면은 앱 활동을 특정 상태에 배치하는 것입니다. 테스트의 이 '특정' 부분을 정의하려면 AndroidX 테스트 라이브러리의 일부인 ActivityScenario 인스턴스를 사용하세요. 이 클래스를 사용하면 기기 수준 이벤트를 시뮬레이션하는 상태에 활동을 배치할 수 있습니다.

ActivityScenario는 로컬 단위 테스트 및 기기 내 통합 테스트에서 모두 사용할 수 있는 크로스 플랫폼 API입니다. 실제 또는 가상 기기에서 ActivityScenario는 스레드 안전을 제공하여 테스트의 계측 스레드와 테스트 중인 활동을 실행하는 스레드 간의 이벤트를 동기화합니다.

이 API는 특히 테스트 중인 활동이 소멸되거나 생성될 때 어떻게 동작하는지 평가하는 데 적합합니다. 이 섹션에서는 이 API와 관련된 가장 일반적인 사용 사례를 보여줍니다.

활동 생성

테스트에 사용되는 활동을 생성하려면 다음 스니펫에 나와 있는 코드를 추가합니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
       launchActivity<MyActivity>().use {
       }
    }
}

활동을 만든 후 ActivityScenario는 활동을 RESUMED 상태로 전환합니다. 이 상태는 활동이 실행 중이며 사용자에게 표시됨을 나타냅니다. 이 상태에서는 Espresso UI 테스트를 사용하여 활동의 View 요소와 자유롭게 상호작용할 수 있습니다.

테스트가 완료되면 활동에서 close를 호출하는 것이 좋습니다. 이렇게 하면 관련 리소스가 삭제되고 테스트의 안정성이 향상됩니다. ActivityScenarioCloseable를 구현하므로 활동이 자동으로 닫히도록 use 확장 프로그램 또는 자바 프로그래밍 언어의 try-with-resources을 적용할 수 있습니다.

또는 ActivityScenarioRule를 사용하여 각 테스트 전에 ActivityScenario.launch를 자동으로 호출하고 테스트 해제 시 ActivityScenario.close를 자동으로 호출할 수 있습니다. 다음 예에서는 규칙을 정의하고 규칙에서 시나리오의 인스턴스를 가져오는 방법을 보여줍니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule var activityScenarioRule = activityScenarioRule<MyActivity>()

    @Test fun testEvent() {
        val scenario = activityScenarioRule.scenario
    }
}

새로운 상태로 활동 변경

활동을 CREATED 또는 STARTED와 같은 다른 상태로 유도하려면 moveToState()를 호출합니다. 이 작업은 활동이 다른 앱이나 시스템 작업에 의해 중단되어 각각 중지되거나 일시중지된 상황을 시뮬레이션합니다.

moveToState()의 사용 예는 다음 코드 스니펫에 나와 있습니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.moveToState(State.CREATED)
        }
    }
}

현재 활동 상태 확인

테스트 중인 활동의 현재 상태를 확인하려면 ActivityScenario 객체 내의 state 필드 값을 가져옵니다. 다음 코드 스니펫에서와 같이 활동이 다른 활동으로 리디렉션되거나 자체적으로 종료되는 경우 테스트 중인 활동의 상태를 확인하는 것이 특히 유용합니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              startActivity(Intent(activity, MyOtherActivity::class.java))
            }

            val originalActivityState = scenario.state
        }
    }
}

활동 다시 생성

기기의 리소스가 부족하면 시스템은 활동을 소멸시켜 사용자가 앱으로 돌아올 때 앱에서 해당 활동을 다시 만들어야 할 수 있습니다. 이러한 조건을 시뮬레이션하려면 recreate()를 호출합니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.recreate()
        }
    }
}

ActivityScenario 클래스는 활동의 저장된 인스턴스 상태와 @NonConfigurationInstance를 사용하여 주석이 달린 객체를 유지합니다. 이러한 객체는 테스트 중인 활동의 새 인스턴스에 로드됩니다.

활동 결과 검색

완료된 활동과 연결된 결과 코드 또는 데이터를 가져오려면 다음 코드 스니펫에서와 같이 ActivityScenario 객체 내의 result 필드 값을 가져옵니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testResult() {
        launchActivity<MyActivity>().use {
            onView(withId(R.id.finish_button)).perform(click())

            // Activity under test is now finished.

            val resultCode = scenario.result.resultCode
            val resultData = scenario.result.resultData
        }
    }
}

활동에서 작업 트리거

ActivityScenario 내의 모든 메서드는 차단 호출이므로 API를 사용하려면 계측 스레드에서 메서드를 실행해야 합니다.

테스트 중인 활동에서 작업을 트리거하려면 Espresso 뷰 매처를 사용하여 뷰의 요소와 상호작용하세요.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use {
            onView(withId(R.id.refresh)).perform(click())
        }
    }
}

그러나 활동 자체에서 메서드를 호출해야 한다면 ActivityAction를 구현하여 안전하게 호출할 수 있습니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              activity.handleSwipeToRefresh()
            }
        }
    }
}