앱 Fragment 테스트

Fragment는 앱 내에서 재사용 가능한 컨테이너 역할을 하며, 다양한 Activity와 레이아웃 구성에서 동일한 사용자 인터페이스 레이아웃을 표시할 수 있습니다. Fragment는 다양한 용도로 사용되기 때문에 일관적이고 효율적으로 리소스를 사용하는 환경을 제공하는지 검증하는 것이 중요합니다.

  • Fragment의 외형은 대형 스크린 화면이나 기기에서 가로 모드를 지원하는 Fragment를 포함하여 모든 레이아웃 구성에서 일관적이어야 합니다.
  • Fragment가 사용자에게 보일 경우에만 Fragment의 뷰 계층 구조를 생성하세요.

이 문서에서는 각 Fragment의 동작을 평가하는 테스트에서 프레임워크를 제공하는 API를 포함하는 방법을 설명합니다.

Fragment 상태 변경

이 테스트를 수행하기 위한 조건을 쉽게 설정할 수 있도록 AndroidX는 Fragment를 생성하고 상태를 변경하기 위한 FragmentScenario 라이브러리를 제공합니다.

테스트 아티팩트 위치 구성

FragmentScenario를 의도한 대로 사용하려면 다음 코드 스니펫과 같이 앱의 테스트 APK에서 Fragment 테스트 아티팩트를 정의합니다.

app/build.gradle

dependencies {
    // ...
    debugImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha07'
}

Fragment 만들기

FragmentScenario에는 다음 유형의 Fragment를 실행하기 위한 메서드가 포함됩니다.

이 메서드는 다음과 같은 유형의 Fragment도 지원합니다.

  • 그래픽 Fragment, 사용자 인터페이스 포함. 이 유형의 Fragment를 실행하려면 launchFragmentInContainer()를 호출합니다. FragmentScenario는 Fragment를 Activity의 루트 뷰 컨트롤러에 연결합니다. 그 외의 경우에는 이 포함된 Activity가 비어 있습니다.
  • 그래픽이 아닌 Fragment(또는 경우에 따라 헤드가 없는 Fragment), 여러 Activity에 포함된 정보를 단기적으로 실행. 이 유형의 Fragment를 실행하려면 launchFragment()를 호출합니다. FragmentScenario는 이 유형의 Fragment를 완전히 비어 있는 Activity(루트 뷰가 없는 Activity)에 연결합니다.

이런 Fragment 유형 중 하나를 실행한 이후 FragmentScenario는 테스트 중인 Fragment를 RESUMED 상태로 변경합니다. 이 상태는 Fragment가 실행 중이라는 것을 나타냅니다. 그래픽 Fragment를 테스트할 때 사용자에게도 테스트가 보이므로 Espresso UI 테스트를 사용하여 UI 요소에 대한 정보를 평가할 수 있습니다.

다음 코드 스니펫은 각 유형의 Fragment를 실행하는 방법을 보여줍니다.

그래프 Fragment 예시

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" and "factory" arguments are optional.
        val fragmentArgs = Bundle().apply {
            putInt("selectedListItem", 0)
        }
        val factory = MyFragmentFactory()
        val scenario = launchFragmentInContainer<MyFragment>(
                fragmentArgs, factory)
        onView(withId(R.id.text)).check(matches(withText("Hello World!")))
    }
}

그래프가 아닌 Fragment 예시

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" and "factory" arguments are optional.
        val fragmentArgs = Bundle().apply {
            putInt("numElements", 0)
        }
        val factory = MyFragmentFactory()
        val scenario = launchFragment<MyFragment>(fragmentArgs, factory)
    }
}

Fragment 다시 생성

기기에 리소스가 부족할 경우 시스템이 Fragment가 포함된 Activity를 소멸할 수 있으며, 이때 사용자가 앱에 돌아왔을 때 앱이 해당 Fragment를 다시 생성해야 합니다. 이 상황을 시뮬레이션하려면 recreate()를 호출하세요.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.recreate()
    }
}

FragmentScenario 클래스가 테스트 중인 Fragment를 다시 생성하면 Fragment는 다시 생성하기 이전의 수명 주기 상태로 돌아옵니다.

Fragment를 새 상태로 변경

앱 UI 테스트에서는 대개 테스트 중인 Fragment를 실행하고 다시 생성하는 것만으로 충분합니다. 그러나 세밀한 유닛 테스트에서는 Fragment가 하나의 수명 주기에서 다른 수명 주기로 넘어갈 때 Fragment의 동작을 평가할 수 있습니다.

Fragment를 다른 수명 주기 상태로 변경하려면 moveToState()를 호출하세요. 이 메서드는 CREATED, STARTED, RESUMED, DESTROYED의 상태를 인수로 지원합니다. 이 작업은 Fragment가 포함된 Activity가 다른 앱이나 시스템 작업에 중단되어 상태를 변경하는 상황을 시뮬레이션합니다.

moveToState()의 사용 예시가 다음 코드 스니펫에서 나타납니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.moveToState(State.CREATED)
    }
}

Fragment에서 작업 트리거

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

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        onView(withId(R.id.refresh))
                .perform(click())
    }
}

Fragment 자체에서 메서드를 호출해야 할 경우 (예: 옵션 메뉴에서의 선택에 응답) FragmentAction을 구현하면 안전하게 호출할 수 있습니다.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.onFragment(fragment ->
            fragment.onOptionsItemSelected(clickedItem) {
                // Update fragment's state based on selected item.
            }
        }
    }
}