Hello, Espresso
: Android UI 테스트를 작성을 지원해주는 라이브러리
기본사용
- 테스트 작성자에게 사용자가 애플리케이션과 상호작용(UI 요소를 찾고 상호작용)하는 동안 실행하는 작업의 관점에서 생각하도록 권장합니다.
- 동시에 프레임워크가 애플리케이션의 활동 및 뷰에 직접 액세스하지 못하도록 방지합니다. 이러한 객체를 계속 보유하고 UI 스레드에서 벗어나 이러한 객체에 관해 작업하는 것이 테스트 취약성의 주요 원인이기 때문입니다.
따라서 getView() 및 getCurrentActivity()와 같은 메서드는 Espresso API에 표시되지 않습니다. ViewAction 및 ViewAssertion의 자체 서브클래스를 구현하여 뷰 관련 작업을 안전하게 실행할 수 있습니다.
Espresso의 기본 구성요소
- Espresso
- 뷰와의 상호작용을 위한 진입점
- onView() 및 onData()
- 반드시 뷰와 연결되지 않아도 되는 API를 노출함 (ex: pressBack()).
- ViewMatchers
- Matcher<? super View> 인터페이스를 구현하는 객체의 컬렉션
- 이 중 하나 이상을 onView()메서드에 전달하여 현재 뷰 계층 구조 내에서 뷰를 찾을 수 있습니다.
- ViewActions
- ViewInteraction.perform()메서드에 전달할 수 있는 ViewAction객체의 컬렉션 (ex: click()).
- ViewAssertions
- ViewInteraction.check() 메서드에 전달할 수 있는 ViewAssertion 객체의 컬렉션
- 'ViewMatchers'를 사용하여 현재 선택된 뷰의 상태를 어설션하는 matches 어설션을 대부분 사용합니다.
Sample)
// 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()))
step1) 뷰 찾기
onView(final Matcher<View> viewMatcher) 메서드
// TODO change parameter to type to Matcher<? extends View> which currently causes Dagger issues
@CheckReturnValue
@CheckResult
public static ViewInteraction onView(final Matcher<View> viewMatcher) {
return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction();
}
onData(Matcher<? extends Object> dataMatcher) 메서드
@CheckReturnValue
@CheckResult
public static DataInteraction onData(Matcher<? extends Object> dataMatcher) {
return new DataInteraction(dataMatcher);
}
: 현재 뷰 계층 구조 내에서 단 하나의 뷰를 찾아줍니다.
- 타겟 뷰가 AdapterView(예: ListView, GridView 또는 Spinner) 내부에 있을 경우에는 대신 onData()를 사용해야 합니다.
(이런 경우에선 onView() 메서드가 작동하지 않을 수 있습니다.)
- hamcrest 매처를 사용하며, 이 매처는 강력하며 Mockito 또는 JUnit과 함께 사용해 본 사용자에게 익숙합니다.
- 고려사항
- 'Text 로 뷰 찾기'에서 검색 범위를 좁힐 수 없으면 접근성 버그로 처리하는 것을 고려하세요.
- 원하는 하나의 뷰를 찾는 최소 설명 매처를 사용하세요.
뷰 찾기에서 onView()와 onData()의 파라미터로 사용할 "ViewMatcher"
ID로 뷰 찾기 : onView(withId(R.id.아이디명))
- 흔히 원하는 뷰에는 고유한 R.id가 있으며 단순 withId 매처는 뷰 검색 범위를 좁힙니다.
그러나 테스트 개발 시 R.id를 확인할 수 없는 정당한 사례가 많이 있습니다. 예를 들어 특정 뷰에 R.id가 없거나 R.id가 고유하지 않을 수 있습니다. 따라서 뷰를 보유하는 활동 또는 프래그먼트의 비공개 멤버에 액세스하거나 알려진 R.id로 컨테이너를 찾아 특정 뷰와 관련된 콘텐츠로 이동해야 할 수 있습니다.
Text 로 뷰 찾기 : onView(withText("텍스트값"))
- 접근성이 적용된 앱에서는 사용자가 상호작용할 수 있는 모든 뷰에 설명 텍스트가 포함되어 있거나 콘텐츠 설명이 있어야 합니다.
따라서, withText() 또는 withContentDescription()을 사용하여 검색 범위를 좁힐 수 없으면 접근성 버그로 처리하는 것을 고려하세요.
예제들
ex1) ViewMatcher 객체 또는 자체 맞춤 객체를 통해 뷰 범위를 좁힐 수 있도록 해준다.
onView(withId(R.id.my_view))
ex2) 뷰 중 하나에 "Hello!" 텍스트가 있습니다. 이 텍스트를 사용하여 조합 매처를 통해 검색 범위를 좁힐 수 있습니다.
onView(allOf(withId(R.id.my_view), withText("Hello!")))
ex3) 매처 조건을 넓히거나 inverse하여 사용가능
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
step2) 뷰에 작업 실행
perform(final ViewAction... viewActions) 메소드
- step1번에서 타겟 뷰에 적합한 매처를 찾았으면perform 메서드를 사용하여 이 뷰에ViewAction인스턴스를 실행할 수 있습니다.
- 한 번의 perform 호출로 둘 이상의 작업을 실행할 수 있습니다.
- 주의 : 작업 중인 뷰가ScrollView(세로 또는 가로) 내부에 있다면, click() 및 typeText()와 같이 뷰가 표시되어야 하는 작업 앞에scrollTo()를 사용하는 것을 고려하세요. 이렇게 하면 다른 작업을 진행하기 전에 뷰가 표시됩니다
public ViewInteraction perform(final ViewAction... viewActions) {
checkNotNull(viewActions);
for (ViewAction va : viewActions) {
SingleExecutionViewAction singleExecutionViewAction =
new SingleExecutionViewAction(va, viewMatcher);
desugaredPerform(singleExecutionViewAction);
}
return this;
}
onView(...).perform(click())
ex2) 한 번의 perform 호출로 둘 이상의 작업을 실행
onView(...).perform(typeText("Hello"), click())
ex3) 스크롤 뷰 내부의 뷰를 클릭할 때
onView(...).perform(scrollTo(), click())
뷰에 작업 실행에서 perform()의 파라미터로 사용하는 "ViewActions"
- 일부 대표적인 ViewActions 메서드들의 목록
|
- 여기서 더 많은 ViewActions들을 확인가능합니다.
step3) ViewAssertions(뷰 어설션) 확인
: UI 테스트 중에 UI 요소의 상태 및 속성을 검증하는 데 사용되는 클래스
즉, ViewAssertions 클래스의 메서드를 사용하여 UI 요소의 특정 조건을 확인하고, 테스트 결과를 검증할 수 있습니다.
check(final ViewAssertion viewAssert)메서드
: 현재 선택된 뷰에 어설션을 적용할 수 있습니다.
matches(final Matcher<? super View> viewMatcher) 메서드
: ViewMatcher 객체를 사용하여 현재 선택된 뷰의 상태를 어설션합니다.
- 가장 많이 사용되는 어설션
Ex) 뷰에 "Hello!" 텍스트가 있는지 확인
onView(...).check(matches(withText("Hello!")))
|
- ViewAssertions 메서드를 활용하여 테스트 코드에서 예상한 상태를 검증하고, UI 동작의 정확성을 확인할 수 있습니다.
- 사용 예) 버튼 클릭 후 특정 텍스트가 표시되는지 검증하거나, 체크박스의 선택 상태를 확인하는 등의 검증을 수행할 때 ViewAssertions를 사용합니다.
- ViewAssertions와 함께 사용되는 ViewMatchers와 ViewActions와 조합하여 다양한 UI 상호작용과 검증을 수행할 수 있습니다.
- 주의1) 'Assertions'을onView()인수에 넣지 마세요. => 대신 확인할 사항을 check 블록 내에 명확하게 지정하세요.
ㄴ> Ex)
// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
ㄴ> "Hello!"가 뷰의 콘텐츠인지 어설션하려는 경우 -> 아래 코드는 잘못된 사례이다.
ㄴ> 그러나, 뷰 공개 상태 플래그를 변경한 후 "Hello!"라는 텍스트가 있는 뷰가 표시되는지 어설션하려는 경우 -> 아래 코드는 올바른 사례이다.
- 주의2) 뷰가 표시되지 않는지 어설션하는 것과 뷰가 뷰 계층 구조에 없는지 어설션하는 것의 차이에 주의해야 합니다.
디버깅
- Espresso는 테스트 실패 시 유용한 디버깅 정보를 제공합니다.
로깅
Espresso는 모든 뷰 작업을 logcat에 로깅합니다.
ex)
ViewInteraction: Performing 'single click' action on view with text: Espresso
뷰 계층 구조
- onView()실패 시 예외 메시지에 뷰 계층 구조를 인쇄합니다.
- onView()가 타겟 뷰를 찾지 못하면 NoMatchingViewException이 발생합니다.
예외 문자열에서 뷰 계층 구조를 검사하여 매처가 뷰를 일치시키지 못한 이유를 분석할 수 있습니다. - onView()가 지정된 매처와 일치하는 여러 뷰를 찾으면 AmbiguousViewMatcherException이 발생합니다.
뷰 계층 구조가 인쇄되고 일치된 모든 뷰가 MATCHES 라벨로 표시됩니다.
예외 메시지 ex)
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****
- AdapterView 위젯이 뷰 계층 구조에 있을 때 onData()를 사용하지 않고 onView()를 사용하는 경우,
Espresso는 사용자에게 AdapterView 위젯의 존재에 관해 경고합니다. onView() 작업에서 NoMatchingViewException이 발생하고 예외 메시지에는 어댑터 뷰 목록과 함께 경고가 포함됩니다.
배운 Espresso 요약본
ETC
Espresson의 동기화 방법
- onView() 호출할 때마다 다음 동기화 조건이 충족될 때까지 상응하는 UI 작업 또는 어설션을 실행하기 위해 대기합니다.
- 복잡한 뷰 계층 구조 또는 예상치 못한 위젯 동작을 처리할 때 설명을 위해 Android 스튜디오의 LayoutInspector를 사용
출처
- https://developer.android.com/training/testing/espresso/basics?hl=ko
- chatGPT