여러 앱 간의 사용자 상호작용과 관련된 사용자 인터페이스(UI) 테스트를 사용하면 사용자 플로우가 다른 앱이나 시스템 UI로 넘어갈 때 앱이 올바르게 작동하는지 확인할 수 있습니다. 이러한 사용자 플로우의 예로는 사용자가 문자 메시지를 입력하고, 메시지를 보낼 수신자를 선택할 수 있도록 Android 연락처 선택도구를 실행한 후 사용자가 메시지를 제출할 수 있도록 제어권을 원래 앱에 돌려주는 메시지 앱이 있습니다.
이 과정에서는 AndroidX 테스트에서 제공하는 UI Automator 테스트 프레임워크를 사용하여 이러한 UI 테스트를 작성하는 방법을 다룹니다.
UI Automator API를 사용하면 어떤 Activity
에 포커스가 있는지 관계없이 기기에 표시되는 요소와 상호작용할 수 있습니다. 테스트는 UI 구성요소에 표시되는 텍스트 또는 콘텐츠 설명과 같은 편리한 설명어를 사용하여 UI 구성요소를 찾을 수 있습니다. UI Automator 테스트는 Android 4.3(API 수준 18) 이상을 실행하는 기기에서 실행될 수 있습니다.
UI Automator 테스트 프레임워크는 계측 기반 API이며 AndroidJUnitRunner
테스트 실행기와 함께 작동합니다.
또한 UI Automator API 참조를 읽고 UI Automator 코드 샘플을 사용해 보세요.
UI Automator 설정
UI Automator를 사용하여 UI 테스트를 빌드하기 전에 AndroidX 테스트용 프로젝트 설정에 설명된 대로 테스트 소스 코드 위치 및 프로젝트 종속 항목을 구성해야 합니다.
Android 앱 모듈의 build.gradle
파일에서 UI Automator 라이브러리의 종속 항목 참조를 설정해야 합니다.
dependencies { ... androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' }
UI Automator 테스트를 최적화하려면 먼저 타겟 앱의 UI 구성요소를 검사하고 이 UI 구성요소에 액세스할 수 있는지 확인해야 합니다. 이러한 최적화 도움말은 다음 두 개의 섹션에 설명되어 있습니다.
기기의 UI 검사
테스트를 설계하기 전에 기기에 표시되는 UI 구성요소를 검사합니다. UI Automator 테스트에서 이러한 구성요소에 액세스할 수 있는지 확인하려면 이러한 구성요소에 표시되는 텍스트 라벨, android:contentDescription
값 또는 둘 다 있는지 검사합니다.
uiautomatorviewer
도구는 레이아웃 계층 구조를 검사하고 기기의 포그라운드에 표시되는 UI 구성요소의 속성을 보기에 편리한 시각적 인터페이스를 제공합니다.
이 정보를 바탕으로 UI Automator를 사용하여 더욱 세분화된 테스트를 만들 수 있습니다. 예를 들어 표시되는 특정 속성과 일치하는 UI 선택기를 만들 수 있습니다.
uiautomatorviewer
도구를 실행하려면 다음 단계를 따르세요.
- 실제 기기에서 타겟 앱을 실행합니다.
- 기기를 개발 머신에 연결합니다.
- 터미널 창을 열고
<android-sdk>/tools/
디렉터리로 이동합니다. - 다음 명령어를 사용하여 도구를 실행합니다.
$ uiautomatorviewer
애플리케이션의 UI 속성을 보려면 다음 단계를 따르세요.
uiautomatorviewer
인터페이스에서 Device Screenshot 버튼을 클릭합니다.- 왼쪽 패널에 있는 스냅샷 위로 마우스를 가져가서
uiautomatorviewer
도구로 식별된 UI 구성요소를 확인합니다. 속성은 오른쪽 하단 패널에 나열되고 레이아웃 계층 구조는 오른쪽 상단 패널에 나열됩니다. - 선택적으로 Toggle NAF Nodes 버튼을 클릭하여 UI Automator에 액세스할 수 없는 UI 구성요소를 확인합니다. 이러한 구성요소의 경우에는 제한된 정보만 사용할 수 있습니다.
Android에서 제공하는 일반적인 UI 구성요소 유형에 관해 알아보려면 사용자 인터페이스를 참조하세요.
활동의 접근성 확인
UI Automator 테스트 프레임워크는 Android 접근성 기능을 구현한 앱에서 더 나은 성능을 제공합니다. View
유형의 UI 요소 또는 SDK의 View
서브클래스를 사용할 때는 접근성 지원을 구현할 필요가 없습니다. 이러한 클래스에는 접근성 지원이 이미 구현되어 있기 때문입니다.
그러나 일부 앱에서는 맞춤 UI 요소를 사용하여 더욱 풍부한 사용자 환경을 제공합니다. 이러한 요소는 자동 접근성 지원을 제공하지 않습니다. SDK에서 제공하지 않는 View
의 서브클래스 인스턴스가 앱에 포함되어 있다면 다음 단계를 완료하여 이러한 요소에 접근성 기능을 추가해야 합니다.
ExploreByTouchHelper
를 확장하는 구체적인 클래스를 만듭니다.setAccessibilityDelegate()
를 호출하여 새 클래스의 인스턴스를 특정 맞춤 UI 요소와 연결합니다.
맞춤 뷰 요소에 접근성 기능을 추가하는 방법에 관한 자세한 안내는 액세스 가능한 맞춤 뷰 빌드를 참조하세요. Android의 접근성에 관한 일반 권장사항을 자세히 알아보려면 더욱 접근성 높은 앱 만들기를 참조하세요.
UI Automator 테스트 클래스 만들기
UI Automator 테스트 클래스는 JUnit 4 테스트 클래스와 동일한 방식으로 작성해야 합니다. JUnit 4 테스트 클래스를 만드는 방법과 JUnit 4 어설션 및 주석을 사용하는 방법을 자세히 알아보려면 계측 단위 테스트 클래스 만들기를 참조하세요.
테스트 클래스 정의의 시작 부분에 @RunWith(AndroidJUnit4.class)
주석을 추가합니다. 또한 AndroidX 테스트에서 제공하는 AndroidJUnitRunner
클래스를 기본 테스트 실행기로 지정해야 합니다. 이 단계는 기기 또는 에뮬레이터에서 UI Automator 테스트 실행에 자세히 설명되어 있습니다.
UI Automator 테스트 클래스에서 다음 프로그래밍 모델을 구현하세요.
getInstance()
메서드를 호출하고 이 메서드에Instrumentation
객체를 인수로 전달하여 테스트할 기기에 액세스하기 위한UiDevice
객체를 가져옵니다.findObject()
메서드를 호출하여 기기에 표시되는 UI 구성요소(예: 포그라운드에 있는 현재 뷰)에 액세스하기 위한UiObject
객체를 가져옵니다.UiObject
메서드를 호출하여 UI 구성요소에서 실행할 특정 사용자 상호작용을 시뮬레이션합니다. 예를 들어performMultiPointerGesture()
를 호출하여 멀티터치 동작을 시뮬레이션하고setText()
를 호출하여 텍스트 필드를 수정합니다. 필요에 따라 2단계와 3단계에서 API를 반복적으로 호출하여 여러 UI 구성요소 또는 사용자 작업 시퀀스와 관련된 더욱 복잡한 사용자 상호작용을 테스트할 수 있습니다.- 이러한 사용자 상호작용이 이뤄진 후 UI에 예상 상태 또는 동작이 반영되었는지 확인합니다.
아래 섹션에서 이러한 단계를 더 자세히 다룹니다.
UI 구성요소에 액세스
UiDevice
객체는 기기 상태에 액세스하여 조작하는 기본적인 방법입니다. 테스트에서 UiDevice
메서드를 호출하여 현재 방향 또는 디스플레이 크기와 같은 다양한 속성의 상태를 확인할 수 있습니다.
테스트에서 UiDevice
객체를 사용하여 기기를 특정 회전으로 강제 설정, D패드 하드웨어 버튼 누르기, 홈 및 메뉴 버튼 누르기와 같은 기기 수준 작업을 실행할 수 있습니다.
기기의 홈 화면에서 테스트를 시작하는 것이 좋습니다. 홈 화면(또는 기기에서 선택한 일부 다른 시작 위치)에서 UI Automator API가 제공하는 메서드를 호출하여 특정 UI 요소를 선택하고 이 UI 요소와 상호작용할 수 있습니다.
다음 코드 스니펫은 테스트에서 UiDevice
인스턴스를 가져오고 홈 버튼 누르기를 시뮬레이션하는 방법을 보여줍니다.
Kotlin
import org.junit.Before import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until ... private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample" private const val LAUNCH_TIMEOUT = 5000L private const val STRING_TO_BE_TYPED = "UiAutomator" @RunWith(AndroidJUnit4::class) @SdkSuppress(minSdkVersion = 18) class ChangeTextBehaviorTest2 { private lateinit var device: UiDevice @Before fun startMainActivityFromHomeScreen() { // Initialize UiDevice instance device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) // Start from the home screen device.pressHome() // Wait for launcher val launcherPackage: String = device.launcherPackageName assertThat(launcherPackage, notNullValue()) device.wait( Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT ) // Launch the app val context = ApplicationProvider.getApplicationContext<Context>() val intent = context.packageManager.getLaunchIntentForPackage( BASIC_SAMPLE_PACKAGE).apply { // Clear out any previous instances addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } context.startActivity(intent) // Wait for the app to appear device.wait( Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT ) } }
자바
import org.junit.Before; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; ... @RunWith(AndroidJUnit4.class) @SdkSuppress(minSdkVersion = 18) public class ChangeTextBehaviorTest { private static final String BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"; private static final int LAUNCH_TIMEOUT = 5000; private static final String STRING_TO_BE_TYPED = "UiAutomator"; private UiDevice device; @Before public void startMainActivityFromHomeScreen() { // Initialize UiDevice instance device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // Start from the home screen device.pressHome(); // Wait for launcher final String launcherPackage = device.getLauncherPackageName(); assertThat(launcherPackage, notNullValue()); device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT); // Launch the app Context context = ApplicationProvider.getApplicationContext(); final Intent intent = context.getPackageManager() .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE); // Clear out any previous instances intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); // Wait for the app to appear device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT); } }
이 예에서는 @SdkSuppress(minSdkVersion = 18)
문은 UI Automator 프레임워크에서 요구하는 대로 테스트가 Android 4.3(API 수준 18) 이상이 설치된 기기에서만 실행되도록 하는 데 도움이 됩니다.
특정 선택기 기준과 일치하는 뷰를 나타내는 UiObject
를 검색하려면 findObject()
메서드를 사용합니다. 필요에 따라 앱 테스트의 다른 부분에서 생성한 UiObject
인스턴스를 재사용할 수 있습니다. UI Automator 테스트 프레임워크는 테스트에서 UiObject
인스턴스를 사용하여 UI 요소를 클릭하거나 속성을 쿼리할 때마다 현재 디스플레이에서 일치 항목을 검색합니다.
다음 스니펫은 테스트에서 앱의 취소 버튼과 확인 버튼을 나타내는 UiObject
인스턴스를 구성하는 방법을 보여줍니다.
Kotlin
val cancelButton: UiObject = device.findObject( UiSelector().text("Cancel").className("android.widget.Button") ) val okButton: UiObject = device.findObject( UiSelector().text("OK").className("android.widget.Button") ) // Simulate a user-click on the OK button, if found. if (okButton.exists() && okButton.isEnabled) { okButton.click() }
자바
UiObject cancelButton = device.findObject(new UiSelector() .text("Cancel") .className("android.widget.Button")); UiObject okButton = device.findObject(new UiSelector() .text("OK") .className("android.widget.Button")); // Simulate a user-click on the OK button, if found. if(okButton.exists() && okButton.isEnabled()) { okButton.click(); }
선택기 지정
앱의 특정 UI 구성요소에 액세스하려면 UiSelector
클래스를 사용합니다. 이 클래스는 현재 표시된 UI의 특정 요소에 관한 쿼리를 나타냅니다.
일치하는 요소가 두 개 이상 발견되면 레이아웃 계층 구조의 첫 번째 일치 요소가 타겟 UiObject
로 반환됩니다.
UiSelector
를 구성할 때 여러 특성을 함께 연결하여 상세검색할 수 있습니다. 일치하는 UI 요소가 없으면 UiAutomatorObjectNotFoundException
이 발생합니다.
childSelector()
메서드를 사용하여 여러 UiSelector
인스턴스를 중첩할 수 있습니다. 예를 들어 다음 코드 예에서는 테스트가 현재 표시된 UI에서 첫 번째 ListView
를 찾는 검색을 지정한 후 이 ListView
내에 텍스트 속성 앱으로 UI 요소를 찾는 검색을 지정하는 방법을 보여줍니다.
Kotlin
val appItem: UiObject = device.findObject( UiSelector().className("android.widget.ListView") .instance(0) .childSelector( UiSelector().text("Apps") ) )
자바
UiObject appItem = device.findObject(new UiSelector() .className("android.widget.ListView") .instance(0) .childSelector(new UiSelector() .text("Apps")));
선택기를 지정할 때 텍스트 요소 또는 콘텐츠 설명어 대신 리소스 ID(UI에 할당된 경우)를 사용하는 것이 좋습니다. 모든 요소에 텍스트 요소가 있는 것은 아닙니다(예: 툴바의 아이콘). 텍스트 선택기는 불안정합니다. 따라서 UI가 약간 변경되면 텍스트 선택기로 인해 테스트에 실패할 수 있습니다. 또한 텍스트 선택기는 다양한 언어로 확장되지 않고 번역된 문자열과 일치하지 않을 수 있습니다.
선택기 기준에서 객체 상태를 지정하는 것이 유용할 수 있습니다. 예를 들어 요소를 선택 해제할 수 있도록 선택된 모든 요소의 목록을 선택하려면 인수가 true
로 설정된 상태로 checked()
메서드를 호출합니다.
작업 실행
테스트에서 UiObject
객체를 가져왔으면 UiObject
클래스의 메서드를 호출하여 이 객체가 나타내는 UI 구성요소에서 사용자 상호작용을 실행할 수 있습니다. 다음과 같은 작업을 지정할 수 있습니다.
click()
: UI 요소의 표시된 경계 중심을 클릭합니다.dragTo()
: 이 객체를 임의의 좌표로 드래그합니다.-
setText()
: 필드의 콘텐츠를 지운 후 편집 가능한 필드에 텍스트를 설정합니다. 반대로clearTextField()
메서드는 편집 가능한 필드에서 기존 텍스트를 지웁니다. -
swipeUp()
:UiObject
에서 위로 스와이프합니다. 마찬가지로swipeDown()
,swipeLeft()
및swipeRight()
메서드는 해당하는 작업을 실행합니다.
UI Automator 테스트 프레임워크를 사용하면 getContext()
를 통해 Context
객체를 가져와서 셸 명령어를 사용하지 않고 Intent
를 보내거나 Activity
를 실행할 수 있습니다.
다음 코드 스니펫은 테스트에서 Intent
를 사용하여 테스트 중인 앱을 실행하는 방법을 보여줍니다. 이 접근 방법은 계산기 앱 테스트에만 관심이 있고 런처에는 관심이 없는 경우에 유용합니다.
Kotlin
fun setUp() { ... // Launch a simple calculator app val context = getInstrumentation().context val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } // Clear out any previous instances context.startActivity(intent) device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT) }
자바
public void setUp() { ... // Launch a simple calculator app Context context = getInstrumentation().getContext(); Intent intent = context.getPackageManager() .getLaunchIntentForPackage(CALC_PACKAGE); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances context.startActivity(intent); device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT); }
컬렉션 작업 실행
항목 컬렉션(예: 음악 앨범의 노래 또는 받은편지함의 이메일 목록)의 사용자 상호작용을 시뮬레이션하려면 UiCollection
클래스를 사용합니다. UiCollection
객체를 생성하려면 하위 UI 요소를 포함하는 레이아웃 뷰와 같은 다른 하위 UI 요소의 래퍼 또는 UI 컨테이너를 검색하는 UiSelector
를 지정합니다.
다음 코드 스니펫은 테스트에서 FrameLayout
내에 표시되는 동영상 앨범을 나타내는 UiCollection
을 구성하는 방법을 보여줍니다.
Kotlin
val videos = UiCollection(UiSelector().className("android.widget.FrameLayout")) // Retrieve the number of videos in this collection: val count = videos.getChildCount( UiSelector().className("android.widget.LinearLayout") ) // Find a specific video and simulate a user-click on it val video: UiObject = videos.getChildByText( UiSelector().className("android.widget.LinearLayout"), "Cute Baby Laughing" ) video.click() // Simulate selecting a checkbox that is associated with the video val checkBox: UiObject = video.getChild( UiSelector().className("android.widget.Checkbox") ) if (!checkBox.isSelected) checkBox.click()
자바
UiCollection videos = new UiCollection(new UiSelector() .className("android.widget.FrameLayout")); // Retrieve the number of videos in this collection: int count = videos.getChildCount(new UiSelector() .className("android.widget.LinearLayout")); // Find a specific video and simulate a user-click on it UiObject video = videos.getChildByText(new UiSelector() .className("android.widget.LinearLayout"), "Cute Baby Laughing"); video.click(); // Simulate selecting a checkbox that is associated with the video UiObject checkBox = video.getChild(new UiSelector() .className("android.widget.Checkbox")); if(!checkBox.isSelected()) checkbox.click();
스크롤 가능한 뷰 작업 실행
디스플레이에서 세로 또는 가로 방향 스크롤을 시뮬레이션하려면 UiScrollable
클래스를 사용합니다. 이 기법은 UI 요소가 화면 밖에 위치할 때 이 UI 요소를 스크롤하여 뷰에 표시해야 하는 경우에 유용합니다.
다음 코드 스니펫은 Settings 메뉴에서 아래로 스크롤하여 About tablet 옵션을 클릭하는 과정을 시뮬레이션하는 방법을 보여줍니다.
Kotlin
val settingsItem = UiScrollable(UiSelector().className("android.widget.ListView")) val about: UiObject = settingsItem.getChildByText( UiSelector().className("android.widget.LinearLayout"), "About tablet" ) about.click()
자바
UiScrollable settingsItem = new UiScrollable(new UiSelector() .className("android.widget.ListView")); UiObject about = settingsItem.getChildByText(new UiSelector() .className("android.widget.LinearLayout"), "About tablet"); about.click();
결과 확인
InstrumentationTestCase
는 TestCase
를 확장하므로 표준 JUnit Assert
메서드를 사용하여 앱의 UI 구성요소가 예상 결과를 반환하는지 테스트할 수 있습니다.
다음 코드 스니펫은 테스트가 계산기 앱에서 여러 버튼을 찾아 순서대로 클릭한 후 올바른 결과가 표시되는지 확인하는 방법을 보여줍니다.
Kotlin
private const val CALC_PACKAGE = "com.myexample.calc" fun testTwoPlusThreeEqualsFive() { // Enter an equation: 2 + 3 = ? device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("two")).click() device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("plus")).click() device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("three")).click() device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("equals")).click() // Verify the result = 5 val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result")) assertEquals("5", result.text) }
자바
private static final String CALC_PACKAGE = "com.myexample.calc"; public void testTwoPlusThreeEqualsFive() { // Enter an equation: 2 + 3 = ? device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("two")).click(); device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("plus")).click(); device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("three")).click(); device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("equals")).click(); // Verify the result = 5 UiObject result = device.findObject(By.res(CALC_PACKAGE, "result")); assertEquals("5", result.getText()); }
기기 또는 에뮬레이터에서 UI Automator 테스트 실행
Android 스튜디오 또는 명령줄에서 UI Automator 테스트를 실행할 수 있습니다. AndroidJUnitRunner
를 프로젝트의 기본 계측 실행기로 지정해야 합니다.
참고 자료
Android 테스트에서 UI Automator를 사용하는 방법에 관한 자세한 내용은 다음 자료를 참조하세요.
샘플
- BasicSample: 기본 UI Automator 샘플입니다.