Android/Developers 공부하기

(찌이인한) 에스프레소 테스트 기초 개념

네모메모 2023. 8. 13. 23:19
반응형

 

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;
  }

 

ex1) 뷰를 클릭하려면 다음을 실행
    onView(...).perform(click())

ex2) 한 번의 perform 호출로 둘 이상의 작업을 실행

    onView(...).perform(typeText("Hello"), click())

ex3) 스크롤 뷰 내부의 뷰를 클릭할 때

    onView(...).perform(scrollTo(), click())

 


뷰에 작업 실행에서 perform()의 파라미터로 사용하는 "ViewActions"

- 일부 대표적인 ViewActions 메서드들의 목록

  1. click(): 클릭 이벤트를 시뮬레이션합니다. 버튼, 링크 등을 클릭하여 상호작용을 검증할 때 사용됩니다.
  2. doubleClick(): 더블 클릭 이벤트를 시뮬레이션합니다. 뷰를 두 번 연속으로 클릭할 때 사용됩니다.
  3. longClick(): 롱 클릭 이벤트를 시뮬레이션합니다. 뷰를 길게 누르는 동작을 검증할 때 사용됩니다.
  4. typeText(text): 텍스트를 입력하는 동작을 시뮬레이션합니다.
    EditText 등의 입력 필드에 텍스트를 입력하거나 변경할 때 사용됩니다.
  5. replaceText(text): 기존 텍스트를 지우고 새로운 텍스트를 입력하는 동작을 시뮬레이션합니다.
  6. clearText(): 텍스트 입력 필드의 텍스트를 모두 지우는 동작을 시뮬레이션합니다.
  7. pressKey(keyCode): 특정 키를 누르는 동작을 시뮬레이션합니다. 뒤로가기 키나 엔터 키를 누르는 상황을 검증할 때 사용됩니다.
  8. swipeLeft(), swipeRight(), swipeUp(), swipeDown(): 스와이프 동작을 시뮬레이션합니다.
    화면 스크롤이나 리스트 아이템 스와이프 등을 검증할 때 사용됩니다.
  9. scrollTo(): 특정 뷰나 위치까지 스크롤하는 동작을 시뮬레이션합니다.
  10. closeSoftKeyboard(): 소프트 키보드를 닫는 동작을 시뮬레이션합니다.
  11. openLinkWithText(linkText): 주어진 텍스트를 가진 링크를 클릭하는 동작을 시뮬레이션합니다.

- 여기서 더 많은 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!")))

 

  1. matches(matcher): 주어진 매처(Matcher)와 일치하는지 검증합니다. UI 요소의 텍스트, 가시성, 선택 상태 등을 확인할 때 사용됩니다.
  2. doesNotExist(): UI 요소가 화면에 존재하지 않음을 검증합니다.
  3. isSelected(), isNotSelected(): UI 요소의 선택 상태를 검증합니다.
  4. isChecked(), isNotChecked(): 체크박스나 라디오 버튼 등의 선택 상태를 검증합니다.
  5. isEnabled(), isDisabled(): UI 요소의 활성화/비활성화 상태를 검증합니다.
  6. isClickable(): UI 요소가 클릭 가능한지를 검증합니다.
  7. isDisplayed(), isNotDisplayed(): UI 요소의 가시성 상태를 검증합니다.
  8. hasContentDescription(text): UI 요소의 콘텐트 설명을 검증합니다.
  9. matches(isDisplayed()), matches(not(isDisplayed())): UI 요소가 화면에 표시되는지 또는 표시되지 않는지를 검증합니다.

 

- 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 작업 또는 어설션을 실행하기 위해 대기합니다.

  • 메시지 대기열이 비어 있습니다.
  • 현재 작업을 실행하는 AsyncTask 인스턴스가 없습니다.
  • 모든 개발자 정의 유휴 리소스가 비활성 상태입니다.

- 복잡한 뷰 계층 구조 또는 예상치 못한 위젯 동작을 처리할 때 설명을 위해 Android 스튜디오의 LayoutInspector를 사용

 

 

 

 

 

 

 

 

출처

- https://developer.android.com/training/testing/espresso/basics?hl=ko 

- chatGPT

 

 

반응형