상호 운용성

Compose는 일반적인 테스트 프레임워크와 통합됩니다.

Espresso와의 상호 운용성

하이브리드 앱에서는 뷰 계층 구조 내부의 Compose 구성요소와 Compose 컴포저블 내부의 뷰를 찾을 수 있습니다 (AndroidView 컴포저블을 통해).

두 유형을 일치시키기 위해 특별한 단계가 필요하지는 않습니다. Espresso의 onView로 뷰를 일치시키고 ComposeTestRule로 Compose 요소를 일치시킵니다.

@Test
fun androidViewInteropTest() {
    // Check the initial state of a TextView that depends on a Compose state.
    Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
    // Click on the Compose button that changes the state.
    composeTestRule.onNodeWithText("Click here").performClick()
    // Check the new value.
    Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
}

Compose 상호 운용성 테스트를 위한 뷰 범위 시맨틱 추가

Compose 검색 범위를 특정 뷰로 지정

복잡한 UI를 Compose로 이전할 때 RecyclerView 또는 ViewPager과 같은 여러 기존 Android 뷰 내부에 중첩된 동일한 Compose 요소가 발생할 수 있습니다. 이러한 시나리오에서는 onNodeWithText("Save")와 같은 표준 Compose 검색이 'Multiple nodes found'(여러 노드가 발견됨) 오류와 함께 실패할 수 있습니다.

이러한 요소를 구분하기 위해 동적 테스트 태그를 삽입하도록 프로덕션 코드를 수정하는 대신 Compose 테스트의 범위를 특정 Android 뷰로 직접 지정할 수 있습니다.

테스트 규칙에서 onRootWithViewInteraction API를 사용합니다. 이 함수는 Espresso ViewInteraction를 허용하므로 Espresso를 활용하여 특정 컨테이너 뷰를 격리하고 해당 범위의 계층 구조 내에서만 Compose 상호작용을 실행할 수 있습니다.

목록 항목과 상호작용

특정 RecyclerView 행 내에서 Compose 요소와 상호작용해야 하는 경우 Espresso를 사용하여 행을 찾은 다음 Compose 상호작용을 해당 행으로 범위 지정합니다. 이렇게 하면 다른 모든 행에서 동일한 Compose 요소가 무시됩니다.

@Test
fun testComposeButtonInsideRecyclerViewItem() = runComposeUiTest {
    // Scroll to the desired position using Espresso
    Espresso.onView(withId(recyclerViewId))
        .perform(RecyclerViewActions.scrollToPosition<MyViewHolder>(3))

    // Define an Espresso ViewInteraction that uniquely identifies the row
    val rowView = Espresso.onView(
        allOf(
            withId(rootViewId),
            hasDescendant(withText("Item #3"))
        )
    )

    // Scope the Compose search strictly to that specific row View
    onRootWithViewInteraction(rowView)
        .onNode(hasText("Like"))
        .performClick()
}

ViewPager의 모호성 해결

Compose 레이아웃이 동일한 프래그먼트가 메모리에 동시에 여러 개 있는 경우 검색 범위를 특정 프래그먼트의 루트 뷰 ID로 지정하여 일치 모호성을 방지할 수 있습니다.

@Test
fun testComposeButtonInsideViewPagerItem() = runComposeUiTest {
    // Swipe to the desired page using Espresso
    Espresso.onView(withId(viewPagerViewId)).perform(swipeLeft())

    // Identify the specific container view using Espresso
    val fragmentB = Espresso.onView(withId(fragmentRootViewId))

    // The generic text "Save" is now unique within this view scope
    onRootWithViewInteraction(fragmentB)
        .onNode(hasText("Save"))
        .assertIsDisplayed()
}

UiAutomator와의 상호 운용성

기본적으로 컴포저블은 편리한 설명자 (표시된 텍스트, 콘텐츠 설명 등)를 통해서만 UiAutomator에 액세스할 수 있습니다. Modifier.testTag를 사용하는 모든 컴포저블에 액세스하려면 특정 컴포저블의 하위 트리에 testTagsAsResourceId 시맨틱 속성을 사용 설정해야 합니다. 이 동작을 사용 설정하면 스크롤 가능한 컴포저블과 같이 다른 고유 핸들이 없는 컴포저블 (예: LazyColumn)에 유용합니다.

UiAutomator에서 Modifier.testTag와 함께 중첩된 모든 컴포저블에 액세스할 수 있도록 컴포저블 계층 구조에서 한 번만 시맨틱 속성을 사용 설정하세요.

Scaffold(
    // Enables for all composables in the hierarchy.
    modifier = Modifier.semantics {
        testTagsAsResourceId = true
    }
){
    // Modifier.testTag is accessible from UiAutomator for composables nested here.
    LazyColumn(
        modifier = Modifier.testTag("myLazyColumn")
    ){
        // Content
    }
}

Modifier.testTag(tag)를 포함하는 모든 컴포저블은 resourceName과 동일한 tagBy.res(resourceName)을 사용하여 액세스할 수 있습니다.

val device = UiDevice.getInstance(getInstrumentation())

val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
// Some interaction with the lazyColumn.

추가 리소스

  • Android에서 앱 테스트: Android 테스트 기본사항 및 기법에 관한 광범위한 정보를 제공하는 기본 Android 테스트 방문 페이지입니다.
  • 테스트 기본사항: Android 앱 테스트의 핵심 개념을 자세히 알아봅니다.
  • 로컬 테스트: 일부 테스트는 자체 워크스테이션에서 로컬로 실행할 수 있습니다.
  • 계측 테스트: 계측 테스트도 실행하는 것이 좋습니다. 즉, 기기에서 직접 실행되는 테스트입니다.
  • 지속적 통합: 지속적 통합을 사용하면 테스트를 배포 파이프라인에 통합할 수 있습니다.
  • 다양한 화면 크기 테스트: 사용자가 사용할 수 있는 기기가 많으므로 다양한 화면 크기를 테스트해야 합니다.
  • Espresso: Espresso는 뷰 기반 UI를 위한 것이지만 Compose 테스트의 일부 측면에서는 여전히 유용할 수 있습니다.