UI Automator를 사용하여 자동화된 테스트 작성

UI Automator 테스트 프레임워크는 사용자 앱과 시스템 앱에 관한 상호작용을 실행하는 UI 테스트를 빌드하기 위한 API 세트를 제공합니다.

최신 UI Automator 테스트 소개

UI Automator 2.4에서는 Android용 UI 테스트 작성을 간소화하는 간소화된 Kotlin 친화적 도메인 특정 언어 (DSL)를 도입합니다. 이 새로운 API 노출 영역은 술어 기반 요소 찾기와 앱 상태에 관한 명시적 제어에 중점을 둡니다. 이 API를 사용하여 더 유지관리하기 쉽고 안정적인 자동화된 테스트를 만드세요.

UI Automator를 사용하면 앱의 프로세스 외부에서 앱을 테스트할 수 있습니다. 이렇게 하면 축소가 적용된 출시 버전을 테스트할 수 있습니다. UI Automator도 Macrobenchmark 테스트를 작성할 때 도움이 됩니다.

최신 접근 방식의 주요 기능은 다음과 같습니다.

  • 더 깔끔하고 표현력이 풍부한 테스트 코드를 위한 전용 uiAutomator 테스트 범위
  • 명확한 술어가 있는 UI 요소를 찾는 onElement, onElements, onElementOrNull와 같은 메서드
  • 조건부 요소 onElement*(timeoutMs: Long = 10000)를 위한 기본 제공 대기 메커니즘
  • `waitForStable` 및 ` waitForAppToBeVisible`과 같은 명시적 앱 상태 관리
  • 다중 윈도우 테스트 시나리오를 위한 접근성 윈도우 노드와의 직접 상호작용
  • 시각적 테스트 및 디버깅을 위한 기본 제공 스크린샷 기능 및 ResultsReporter

프로젝트 설정

최신 UI Automator API를 사용하려면 프로젝트의 build.gradle.kts 파일을 업데이트하여 최신 종속 항목을 포함하세요.

Kotlin

dependencies {
  ...
  androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}

Groovy

dependencies {
  ...
  androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}

핵심 API 개념

다음 섹션에서는 최신 UI Automator API의 핵심 개념을 설명합니다.

uiAutomator 테스트 범위

uiAutomator { ... } 블록 내에서 모든 새로운 UI Automator API에 액세스합니다. 이 함수는 테스트 작업에 간결하고 유형 안전한 환경을 제공하는 UiAutomatorTestScope를 만듭니다.

uiAutomator {
  // All your UI Automator actions go here
  startApp("com.example.targetapp")
  onElement { textAsString() == "Hello, World!" }.click()
}

UI 요소 찾기

술어가 있는 UI Automator API를 사용하여 UI 요소를 찾습니다. 이러한 술어를 사용하면 텍스트, 선택 또는 포커스 상태, 콘텐츠 설명과 같은 속성의 조건을 정의할 수 있습니다.

  • onElement { predicate }: 기본 제한 시간 내에 술어와 일치하는 첫 번째 UI 요소를 반환합니다. 함수가 일치하는 요소를 찾지 못하면 예외를 발생시킵니다.

    // Find a button with the text "Submit" and click it
    onElement { textAsString() == "Submit" }.click()
    
    // Find a UI element by its resource ID
    onElement { viewIdResourceName == "my_button_id" }.click()
    
    // Allow a permission request
    watchFor(PermissionDialog) {
      clickAllow()
    }
    
  • onElementOrNull { predicate }: onElement와 비슷하지만 함수가 제한 시간 내에 일치하는 요소를 찾지 못하면 null을 반환합니다. 예외가 발생하지는 않습니다. 선택적 요소에 이 메서드를 사용합니다.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: 하나 이상의 UI 요소가 지정된 술어와 일치할 때까지 기다린 후 일치하는 모든 UI 요소의 목록을 반환합니다.

    // Get all items in a list Ui element
    val listItems = onElements { className == "android.widget.TextView" && isClickable }
    listItems.forEach { it.click() }
    

onElement 호출을 사용하는 몇 가지 도움말은 다음과 같습니다.

  • 중첩된 요소의 onElement 호출 연결: onElement 호출을 연결하여 상위-하위 계층 구조에 따라 다른 요소 내에서 요소를 찾을 수 있습니다.

    // Find a parent Ui element with ID "first", then its child with ID "second",
    // then its grandchild with ID "third", and click it.
    onElement { viewIdResourceName == "first" }
      .onElement { viewIdResourceName == "second" }
      .onElement { viewIdResourceName == "third" }
      .click()
    
  • onElement* 함수의 제한 시간을 밀리초를 나타내는 값 을 전달하여 지정합니다.

    // Find a Ui element with a zero timeout (instant check)
    onElement(0) { viewIdResourceName == "something" }.click()
    
    // Find a Ui element with a custom timeout of 10 seconds
    onElement(10_000) { textAsString() == "Long loading text" }.click()
    

UI 요소와 상호작용

클릭을 시뮬레이션하거나 수정 가능한 필드에 텍스트를 설정하여 UI 요소와 상호작용합니다.

// Click a Ui element
onElement { textAsString() == "Tap Me" }.click()

// Set text in an editable field
onElement { className == "android.widget.EditText" }.setText("My input text")

// Perform a long click
onElement { contentDescription == "Context Menu" }.longClick()

앱 상태 및 감시자 처리

앱의 수명 주기를 관리하고 테스트 중에 표시될 수 있는 예기치 않은 UI 요소를 처리합니다.

앱 수명 주기 관리

API는 테스트 대상 앱의 상태를 제어하는 방법을 제공합니다.

// Start a specific app by package name. Used for benchmarking and other
// self-instrumenting tests.
startApp("com.example.targetapp")

// Start a specific activity within the target app
startActivity(SomeActivity::class.java)

// Start an intent
startIntent(myIntent)

// Clear the app's data (resets it to a fresh state)
clearAppData("com.example.targetapp")

예기치 않은 UI 처리

watchFor API를 사용하면 테스트 흐름 중에 표시될 수 있는 권한 대화상자와 같은 예기치 않은 UI 요소의 핸들러를 정의할 수 있습니다. 이 API는 내부 감시자 메커니즘을 사용하지만 더 많은 유연성을 제공합니다.

import androidx.test.uiautomator.PermissionDialog

@Test
fun myTestWithPermissionHandling() = uiAutomator {
  startActivity(MainActivity::class.java)

  // Register a watcher to click "Allow" if a permission dialog appears
  watchFor(PermissionDialog) { clickAllow() }

  // Your test steps that might trigger a permission dialog
  onElement { textAsString() == "Request Permissions" }.click()

  // Example: You can register a different watcher later if needed
  clearAppData("com.example.targetapp")

  // Now deny permissions
  startApp("com.example.targetapp")
  watchFor(PermissionDialog) { clickDeny() }
  onElement { textAsString() == "Request Permissions" }.click()
}

PermissionDialogT가 객체인 ScopedWatcher<T>의 예입니다.watchFor 이 패턴을 기반으로 맞춤 감시자를 만들 수 있습니다.

앱 공개 상태 및 안정성 대기

테스트에서 요소가 표시되거나 안정화될 때까지 기다려야 하는 경우가 있습니다. UI Automator는 이를 지원하는 여러 API를 제공합니다.

waitForAppToBeVisible("com.example.targetapp")은 지정된 패키지 이름이 있는 UI 요소가 맞춤설정 가능한 제한 시간 내에 화면에 표시될 때까지 기다립니다.

// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")

waitForStable() API를 사용하여 앱의 UI가 상호작용하기 전에 안정적인 것으로 간주되는지 확인합니다.

// Wait for the entire active window to become stable
activeWindow().waitForStable()

// Wait for a specific Ui element to become stable (e.g., after a loading animation)
onElement { viewIdResourceName == "my_loading_indicator" }.waitForStable()

Macrobenchmark 및 기준 프로필에 UI Automator 사용

UI Automator는 앱과 상호작용하고 최종 사용자 관점에서 성능을 측정하는 안정적인 방법을 제공하므로 Jetpack Macrobenchmark 를 사용한 성능 테스트와 기준 프로필 생성에 사용합니다.

Macrobenchmark는 UI Automator API를 사용하여 UI를 구동하고 상호작용을 측정합니다. 예를 들어 시작 벤치마크에서는 onElement를 사용하여 UI 콘텐츠가 완전히 로드되는 시점을 감지하여 완전히 표시하는 데 걸린 시간 (TTFD)을 측정할 수 있습니다. 버벅거림 벤치마크에서는 UI Automator API를 사용하여 목록을 스크롤하거나 애니메이션을 실행하여 프레임 타이밍을 측정합니다. startActivity() 또는 startIntent()와 같은 함수는 측정을 시작하기 전에 앱을 올바른 상태로 만드는 데 유용합니다.

기준 프로필을 생성할 때는 앱의 중요한 사용자 여정 (CUJ)을 자동화하여 사전 컴파일이 필요한 클래스와 메서드를 기록합니다. UI Automator는 이러한 자동화 스크립트를 작성하는 데 적합한 도구입니다. 최신 DSL의 술어 기반 요소 찾기 및 기본 제공 대기 메커니즘 (onElement) 은 다른 메서드에 비해 더 강력하고 결정적인 테스트 실행을 제공합니다. 이 안정성은 불안정성을 줄이고 생성된 기준 프로필 이 가장 중요한 사용자 흐름 중에 실행된 코드 경로를 정확하게 반영하도록 합니다.

고급 기능

다음 기능은 더 복잡한 테스트 시나리오에 유용합니다.

여러 윈도우와 상호작용

UI Automator API를 사용하면 UI 요소와 직접 상호작용하고 검사할 수 있습니다. 이는 PIP 모드 또는 화면 분할 레이아웃과 같이 여러 윈도우가 포함된 시나리오에 특히 유용합니다.

// Find the first window that is in Picture-in-Picture mode
val pipWindow = windows()
  .first { it.isInPictureInPictureMode == true }

// Now you can interact with elements within that specific window
pipWindow.onElement { textAsString() == "Play" }.click()

스크린샷 및 시각적 어설션

테스트 내에서 전체 화면, 특정 윈도우 또는 개별 UI 요소의 스크린샷을 직접 캡처합니다. 이는 시각적 회귀 테스트 및 디버깅에 유용합니다.

uiautomator {
  // Take a screenshot of the entire active window
  val fullScreenBitmap: Bitmap = activeWindow().takeScreenshot()
  fullScreenBitmap.saveToFile(File("/sdcard/Download/full_screen.png"))

  // Take a screenshot of a specific UI element (e.g., a button)
  val buttonBitmap: Bitmap = onElement { viewIdResourceName == "my_button" }.takeScreenshot()
  buttonBitmap.saveToFile(File("/sdcard/Download/my_button_screenshot.png"))

  // Example: Take a screenshot of a PiP window
  val pipWindowScreenshot = windows()
    .first { it.isInPictureInPictureMode == true }
    .takeScreenshot()
  pipWindowScreenshot.saveToFile(File("/sdcard/Download/pip_screenshot.png"))
}

Bitmap의 saveToFile 확장 함수를 사용하면 캡처된 이미지를 지정된 경로에 쉽게 저장할 수 있습니다.

디버깅에 ResultsReporter 사용

ResultsReporter를 사용하면 더 쉽게 검사하고 디버깅할 수 있도록 스크린샷과 같은 테스트 아티팩트를 Android 스튜디오의 테스트 결과와 직접 연결할 수 있습니다.

uiAutomator {
  startApp("com.example.targetapp")

  val reporter = ResultsReporter("MyTestArtifacts") // Name for this set of results
  val file = reporter.addNewFile(
    filename = "my_screenshot",
    title = "Accessible button image" // Title that appears in Android Studio test results
  )

  // Take a screenshot of an element and save it using the reporter
  onElement { textAsString() == "Accessible button" }
    .takeScreenshot()
    .saveToFile(file)

  // Report the artifacts to instrumentation, making them visible in Android Studio
  reporter.reportToInstrumentation()
}

이전 UI Automator 버전에서 마이그레이션

이전 API 노출 영역으로 작성된 기존 UI Automator 테스트가 있는 경우 다음 표를 참조하여 최신 접근 방식으로 마이그레이션하세요.

작업 유형 이전 UI Automator 메서드 새 UI Automator 메서드
진입점 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) uiAutomator { ... } 범위에서 테스트 로직을 래핑합니다.
UI 요소 찾기 device.findObject(By.res("com.example.app:id/my_button")) onElement { viewIdResourceName == "my\_button" }
UI 요소 찾기 device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
유휴 UI 대기 device.waitForIdle() onElement의 기본 제공 제한 시간 메커니즘을 사용하는 것이 좋습니다. 그렇지 않은 경우 activeWindow().waitForStable()을 사용합니다.
하위 요소 찾기 수동으로 중첩된 findObject 호출 onElement().onElement() 체이닝
권한 대화상자 처리 UiAutomator.registerWatcher() watchFor(PermissionDialog)