이 문서에서는 Espresso API로 대체합니다.
Espresso API는 테스트 작성자가
애플리케이션과 상호작용하는 동안 수행 - UI 요소 찾기 및 상호작용
공유할 수 있습니다. 동시에 프레임워크는 활동에 직접 액세스하는 것을 방지합니다.
애플리케이션 뷰를 계속 볼 수 있기 때문에
UI 스레드 밖에서 이루어지는 것이 테스트 결함의 주요 원인입니다. 따라서 다음과 같은 작업이 가능합니다.
Espresso API에서 getView()
및 getCurrentActivity()
와 같은 메서드를 볼 수 없습니다.
자체 서브클래스를 구현하여 뷰에서 계속 안전하게 작업할 수 있습니다.
ViewAction
및 ViewAssertion
API 구성요소
Espresso의 기본 구성요소에는 다음이 포함됩니다.
- Espresso: 뷰와의 상호작용을 위한 진입점입니다 (
onView()
및onData()
). 또한 다음과 같이 뷰에 연결되지 않아도 되는 API를 노출합니다.pressBack()
형식으로 되어 있습니다. - ViewMatchers – ViewMatchers를 구현하는
Matcher<? super View>
인터페이스 이 중 하나 이상을onView()
메서드를 사용하여 현재 뷰 계층 구조 내에서 뷰를 찾습니다. - ViewActions – 다음 항목에 전달할 수 있는
ViewAction
객체의 모음입니다.ViewInteraction.perform()
메서드(예:click()
) - ViewAssertions - 여러 인스턴스에 연결할 수 있는
ViewAssertion
객체의 컬렉션ViewInteraction.check()
메서드를 전달했습니다. 대부분의 경우 뷰 매처를 사용하여 선택할 수 있습니다.
예:
Kotlin
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
자바
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
뷰 찾기
대부분의 경우 onView()
메서드는 hamcrest 매처를 사용합니다.
현재 보기 내에 있는 하나의 보기와만 일치할 것으로 예상됨
계층 구조로 구성됩니다 매처는 강력하며 이전에 사용한 적이 있는 사용자에게 익숙할 것입니다.
Mockito 또는 JUnit을 사용합니다. hamcrest 매처에 익숙하지 않다면
먼저 이 슬라이드와
프레젠테이션 문서를 참조하세요.
원하는 뷰에는 고유한 R.id
가 있는 경우가 많으며 단순 withId
매처는
검색 범위를 좁혀야 합니다 그러나 당신이
테스트 개발 시간에 R.id
를 확인할 수 없습니다. 예를 들어
R.id
가 없거나 R.id
가 고유하지 않을 수 있습니다. 이렇게 하면
계측 테스트는 불안정하고 작성하기 복잡합니다.
findViewById()
를 사용하여 뷰에 액세스할 수 없습니다. 따라서
뷰를 보유하는 활동 또는 프래그먼트의 비공개 멤버에 액세스해야 하거나
알려진 R.id
가 있는 컨테이너를 찾아
확인할 수 있습니다.
Espresso는 개발자가 뷰의 범위를 좁힐 수 있도록 하여 이 문제를 깔끔하게 처리합니다.
기존 ViewMatcher
객체 또는 자체 커스텀 객체 사용
R.id
로 뷰를 찾는 것은 onView()
를 호출하는 것만큼 간단합니다.
Kotlin
onView(withId(R.id.my_view))
자바
onView(withId(R.id.my_view));
때로 R.id
값은 여러 뷰 간에 공유됩니다. 이 경우
특정 R.id
를 사용하려고 하면 다음과 같은 예외가 발생합니다.
AmbiguousViewMatcherException
입니다. 예외 메시지를 통해
현재 뷰 계층 구조로 표현되며, 이 뷰 계층 구조를 검색하여
고유하지 않은 R.id
와 일치하는 뷰입니다.
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
뷰의 다양한 속성을 살펴보면
식별 정보를 제공해야 합니다. 위의 예에서 뷰 중 하나에
"Hello!"
이 기능을 이용하면
매처:
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
자바
onView(allOf(withId(R.id.my_view), withText("Hello!")));
매처를 역전시키지 않도록 선택할 수도 있습니다.
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
자바
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
ViewMatchers
를 참고하세요.
Espresso에서 제공하는 뷰 매처의 클래스
고려사항
- 정상적으로 작동하는 애플리케이션에서 사용자가 상호작용할 수 있는 모든 뷰는
텍스트 또는 콘텐츠 설명이 있어야 합니다. 자세한 내용은
더 많은 사용자를 위해 더욱 접근성 높은 앱 만들기
확인하세요.
withText()
또는withContentDescription()
인 경우 접근성 버그로 처리하는 것이 좋습니다. - 찾고 있는 하나의 뷰를 찾는 최소 설명 매처를 사용합니다.
정의합니다. 프레임워크가 더 많은 작업을 하게 만들므로 지나치게 지정하지 마세요.
반드시 필요합니다 예를 들어 텍스트만으로 보기를 고유하게 식별할 수 있는 경우
뷰도
TextView
에서 할당할 수 있음을 지정할 필요가 없습니다. 많은 경우 뷰의R.id
가 충분해야 합니다. - 타겟 뷰가
AdapterView
내부에 있는 경우(예:ListView
)GridView
또는Spinner
:onView()
메서드가 작동하지 않을 수 있습니다. 이러한 대신onData()
를 사용해야 합니다.
뷰에 작업 실행
타겟 뷰에 적합한 매처를 찾았으면
수행 메서드를 사용하여 거기에 ViewAction
인스턴스를 수행합니다.
예를 들어 뷰를 클릭하려면 다음을 실행합니다.
Kotlin
onView(...).perform(click())
자바
onView(...).perform(click());
한 번의 perform 호출로 둘 이상의 작업을 실행할 수 있습니다.
Kotlin
onView(...).perform(typeText("Hello"), click())
자바
onView(...).perform(typeText("Hello"), click());
작업 중인 뷰가 ScrollView
(세로 또는
뷰가 가로로 놓여 있어야 하는 작업보다 먼저
scrollTo()
와 함께 표시됩니다(예: click()
및 typeText()
). 이
는 다른 작업으로 진행하기 전에 뷰가 표시되는지 확인합니다.
Kotlin
onView(...).perform(scrollTo(), click())
자바
onView(...).perform(scrollTo(), click());
ViewActions
를 참고하세요.
Espresso에서 제공하는 뷰 작업에 사용됩니다.
뷰 어설션 확인
check()
를 사용하여 현재 선택된 뷰에 어설션을 적용할 수 있습니다.
메서드를 사용하여 축소하도록 요청합니다. 가장 많이 사용되는 어설션은 matches()
어설션입니다. Kubernetes는
ViewMatcher
객체로, 현재 선택된 뷰의 상태를 어설션합니다.
예를 들어 뷰에 "Hello!"
텍스트가 있는지 확인하려면 다음 코드를 사용합니다.
Kotlin
onView(...).check(matches(withText("Hello!")))
자바
onView(...).check(matches(withText("Hello!")));
"Hello!"
가 뷰의 콘텐츠인지 어설션하려는 경우 다음은 잘못된 사례로 간주됩니다.
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
자바
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
반면에 "Hello!"
텍스트가 있는 뷰가 다음과 같다고 어설션하려면
예를 들어 뷰 가시성 플래그를 변경한 후
괜찮습니다
뷰 어설션 단순 테스트
이 예에서는 SimpleActivity
에 Button
및 TextView
가 포함되어 있습니다. 이
버튼을 클릭하면 TextView
의 콘텐츠가 "Hello Espresso!"
로 변경됩니다.
다음은 Espresso를 사용하여 이를 테스트하는 방법입니다.
버튼 클릭
첫 번째 단계는 버튼을 찾는 데 도움이 되는 속성을 찾는 것입니다. 이
SimpleActivity
의 버튼에는 예상대로 고유한 R.id
가 있습니다.
Kotlin
onView(withId(R.id.button_simple))
자바
onView(withId(R.id.button_simple));
이제 다음과 같이 클릭을 실행합니다.
Kotlin
onView(withId(R.id.button_simple)).perform(click())
자바
onView(withId(R.id.button_simple)).perform(click());
TextView 텍스트 확인
확인할 텍스트가 포함된 TextView
에도 고유한 R.id
가 있습니다.
Kotlin
onView(withId(R.id.text_simple))
자바
onView(withId(R.id.text_simple));
이제 다음과 같이 콘텐츠 텍스트를 확인합니다.
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
자바
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
어댑터 뷰에서 데이터 로드 확인
AdapterView
는 데이터를 동적으로 로드하는 특수한 유형의 위젯입니다.
어댑터입니다. AdapterView
의 가장 일반적인 예는 ListView
입니다. 따라서
LinearLayout
와 같은 정적 위젯과는 달리
AdapterView
하위 요소를 현재 뷰 계층 구조에 로드할 수 있습니다. 간단한
onView()
검색은 현재 로드되지 않은 뷰를 찾지 못합니다.
Espresso는 다음과 같은 별도의 onData()
진입점을 제공하여 이를 처리합니다.
해당 어댑터 항목을 먼저 로드하여 이전에 포커스를 두었습니다.
모든 하위 요소에 대해
작업을 할 수 있습니다
경고:
AdapterView
에 onData()
문제가 있을 수 있습니다.
메서드가 상속 계약을 위반할 경우, 특히
getItem()
API 이 경우 최선의 조치는
리팩터링할 필요가 없습니다 그렇게 할 수 없는 경우
일치하는 맞춤 AdapterViewProtocol
입니다. 자세한 내용은
기본값 보기
<ph type="x-smartling-placeholder"></ph>
Espresso에서 제공하는 AdapterViewProtocols
클래스
어댑터 뷰 단순 테스트
이 단순 테스트는 onData()
를 사용하는 방법을 보여줍니다. SimpleActivity
에는
커피 음료의 유형을 나타내는 몇 가지 항목이 있는 Spinner
입니다. 사용자가
항목이 선택되면 "One %s a day!"
로 변경되는 TextView
가 있습니다. 여기서
%s
는 선택된 항목을 나타냅니다.
이 테스트의 목표는 Spinner
를 열고 특정 항목을 선택한 후
TextView
에 항목이 포함되어 있는지 확인합니다. Spinner
클래스는
AdapterView
의 경우 다음과 같은 경우 onView()
대신 onData()
를 사용하는 것이 좋습니다.
표시됩니다.
항목 선택 열기
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
자바
onView(withId(R.id.spinner_simple)).perform(click());
항목 선택
항목 선택을 위해 Spinner
는 콘텐츠가 있는 ListView
를 만듭니다.
이 뷰는 매우 길 수 있으며 요소가 뷰에 기여하지 않을 수 있습니다.
계층 구조로 구성됩니다 onData()
를 사용하여 원하는 요소를 뷰에 강제로 적용
계층 구조로 구성됩니다 Spinner
의 항목은 문자열이므로 항목 하나를 일치시키려고 합니다.
다음과 같습니다."Americano"
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
자바
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
텍스트가 올바른지 확인
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
자바
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
디버깅
Espresso는 테스트 실패 시 유용한 디버깅 정보를 제공합니다.
로깅
Espresso는 모든 뷰 작업을 logcat에 로깅합니다. 예:
ViewInteraction: Performing 'single click' action on view with text: Espresso
뷰 계층 구조
Espresso가 onView()
시 예외 메시지에 뷰 계층 구조를 출력합니다.
있습니다
onView()
가 타겟 뷰를 찾지 못하면NoMatchingViewException
은 발생합니다. 예외 문자열에서 뷰 계층 구조를 검사하여 일치자가 어떤 보기도 일치하지 않는 이유입니다.onView()
가 지정된 매처와 일치하는 뷰를 여러 개 찾으면AmbiguousViewMatcherException
이 발생합니다. 뷰 계층 구조가 인쇄되고 일치된 뷰는MATCHES
라벨로 표시됩니다.
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
복잡한 뷰 계층 구조 또는 예상치 못한 위젯 동작을 처리할 때 항상 Android 스튜디오의 Hierarchy Viewer 설명해 드리겠습니다.
어댑터 뷰 경고
Espresso는 사용자에게 AdapterView
위젯의 존재에 관해 경고합니다. onView()
의 경우
작업에서 NoMatchingViewException
이 발생하고 AdapterView
위젯은
가장 일반적인 솔루션은 onData()
를 사용하는 것입니다.
예외 메시지에는 어댑터 뷰 목록과 함께 경고가 포함됩니다.
이 정보를 사용하여 타겟 뷰를 로드하는 onData()
를 호출할 수 있습니다.
추가 리소스
Android 테스트에서 Espresso를 사용하는 방법에 관한 자세한 내용은 확인할 수 있습니다
샘플
- CustomMatcherSample
EditText
객체의 hint 속성과 일치하도록 Espresso를 확장하는 방법을 보여줍니다. - RecyclerViewSample:
Espresso의
RecyclerView
작업 - 더보기