이 문서에서는 다양한 일반 Espresso 테스트를 설정하는 방법을 설명합니다.
뷰를 옆의 다른 뷰와 일치
레이아웃에 그 자체로 고유하지 않은 특정 뷰가 포함될 수 있습니다. 예를 들어 연락처 표에 있는 반복 통화 버튼은 뷰 계층 구조 내의 다른 통화 버튼과 동일한 R.id
와 동일한 텍스트를 포함할 수 있으며 동일한 속성을 가질 수 있습니다.
예를 들어 이 활동에서는 "7"
텍스트가 있는 뷰가 여러 행에서 반복됩니다.
고유하지 않은 뷰는 뷰 옆에 있는 고유한 라벨과 페어링됩니다(예: 통화 버튼 옆에 있는 연락처 이름). 이 경우 hasSibling()
매처를 사용하여 선택 범위를 좁힐 수 있습니다.
Kotlin
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
Java
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click());
작업 모음 내부에 있는 뷰 일치
ActionBarTestActivity
에는 일반 작업 모음과 옵션 메뉴에서 만든 상황별 작업 모음 등 두 가지 다른 작업 모음이 있습니다. 두 작업 모음에는 모두 항상 표시되는 항목 하나와 더보기 메뉴에만 표시되는 항목 두 개가 있습니다. 항목을 클릭하면 TextView가 클릭된 항목의 콘텐츠로 변경됩니다.
다음 코드 스니펫에서와 같이 두 작업 모음에 표시되는 아이콘을 일치시키는 방법은 간단합니다.
Kotlin
fun testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))) }
Java
public void testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))); }
상황별 작업 모음의 코드도 동일해 보입니다.
Kotlin
fun testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))) }
Java
public void testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))); }
일반 작업 모음의 경우 더보기 메뉴의 항목을 클릭하는 것이 조금 더 까다롭습니다. 일부 기기에는 옵션 메뉴에서 더보기 항목을 여는 하드웨어 더보기 메뉴 버튼이 있고 일부 기기에는 일반적인 더보기 메뉴를 여는 소프트웨어 더보기 메뉴 버튼이 있기 때문입니다. 다행히 Espresso가 이 작업을 처리합니다.
일반 작업 모음의 경우 다음과 같습니다.
Kotlin
fun testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext<Context>()) // Click the item. onView(withText("World")) .perform(click()) // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))) }
Java
public void testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext()); // Click the item. onView(withText("World")) .perform(click()); // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))); }
하드웨어 더보기 메뉴 버튼이 있는 기기에서는 다음과 같이 표시됩니다.
상황별 작업 모음의 경우 이 작업이 매우 쉽습니다.
Kotlin
fun testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu() // Click on the item. onView(withText("Key")) .perform(click()) // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))) } }
Java
public void testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu(); // Click on the item. onView(withText("Key")) .perform(click()); // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))); } }
이러한 샘플의 전체 코드를 보려면 GitHub의 ActionBarTest.java
샘플을 확인하세요.
뷰가 표시되지 않는지 어설션
일련의 작업을 실행한 후 테스트 중인 UI의 상태를 어설션하는 것이 좋습니다. 때로는 무언가 발생하지 않을 때와 같은 부정적인 사례일 수 있습니다. ViewAssertions.matches()
를 사용하여 hamcrest 뷰 매처를 ViewAssertion
로 전환할 수 있습니다.
아래 예에서는 isDisplayed()
매처를 가져와서 표준 not()
매처를 사용하여 반전시킵니다.
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import org.hamcrest.Matchers.not onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())))
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.Matchers.not; onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())));
위의 접근 방식은 뷰가 여전히 계층 구조의 일부인 경우에 작동합니다. 그렇지 않은 경우 NoMatchingViewException
이 표시되고 ViewAssertions.doesNotExist()
를 사용해야 합니다.
뷰가 없는지 어설션
작업으로 인해 다른 활동으로 전환되었을 때 발생할 수 있는 뷰 계층 구조에서 뷰가 사라지면 ViewAssertions.doesNotExist()
를 사용해야 합니다.
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers.withId onView(withId(R.id.bottom_left)) .check(doesNotExist())
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.matcher.ViewMatchers.withId; onView(withId(R.id.bottom_left)) .check(doesNotExist());
데이터 항목이 어댑터에 없는지 어설션
특정 데이터 항목이 AdapterView
내에 없다는 것을 증명하려면 약간 다르게 작업해야 합니다. 관심 있는 AdapterView
를 찾아서 이 객체가 보유한 데이터를 조사해야 합니다. onData()
를 사용할 필요는 없습니다.
대신 onView()
를 사용하여 AdapterView
를 찾은 다음 다른 매처를 사용하여 뷰 내부의 데이터 작업을 합니다.
먼저 매처를 찾습니다.
Kotlin
private fun withAdaptedData(dataMatcher: Matcher<Any>): Matcher<View> { return object : TypeSafeMatcher<View>() { override fun describeTo(description: Description) { description.appendText("with class name: ") dataMatcher.describeTo(description) } public override fun matchesSafely(view: View) : Boolean { if (view !is AdapterView<*>) { return false } val adapter = view.adapter for (i in 0 until adapter.count) { if (dataMatcher.matches(adapter.getItem(i))) { return true } } return false } } }
Java
private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("with class name: "); dataMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { if (!(view instanceof AdapterView)) { return false; } @SuppressWarnings("rawtypes") Adapter adapter = ((AdapterView) view).getAdapter(); for (int i = 0; i < adapter.getCount(); i++) { if (dataMatcher.matches(adapter.getItem(i))) { return true; } } return false; } }; }
그런 다음, onView()
를 사용하여 AdapterView
를 찾기만 하면 됩니다.
Kotlin
fun testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))) } }
Java
@SuppressWarnings("unchecked") public void testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))); } }
또한 ID 목록이 있는 어댑터 뷰에 'item: 168'과 같은 항목이 있는 경우 실패하는 어설션이 있습니다.
전체 샘플은 GitHub의 AdapterViewTest.java
클래스 내에 있는 testDataItemNotInAdapter()
메서드를 확인하세요.
맞춤 실패 핸들러 사용
Espresso의 기본 FailureHandler
를 맞춤 항목으로 대체하면 스크린샷을 찍거나 추가 디버그 정보를 전달하는 등 추가 또는 다른 오류 처리가 가능합니다.
CustomFailureHandlerTest
예시는 커스텀 실패 핸들러를 구현하는 방법을 보여줍니다.
Kotlin
private class CustomFailureHandler(targetContext: Context) : FailureHandler { private val delegate: FailureHandler init { delegate = DefaultFailureHandler(targetContext) } override fun handle(error: Throwable, viewMatcher: Matcher<View>) { try { delegate.handle(error, viewMatcher) } catch (e: NoMatchingViewException) { throw MySpecialException(e) } } }
Java
private static class CustomFailureHandler implements FailureHandler { private final FailureHandler delegate; public CustomFailureHandler(Context targetContext) { delegate = new DefaultFailureHandler(targetContext); } @Override public void handle(Throwable error, Matcher<View> viewMatcher) { try { delegate.handle(error, viewMatcher); } catch (NoMatchingViewException e) { throw new MySpecialException(e); } } }
이 실패 핸들러는 NoMatchingViewException
대신 MySpecialException
을 발생시키고 다른 모든 실패를 DefaultFailureHandler
에 위임합니다. CustomFailureHandler
는 테스트의 setUp()
메서드에서 Espresso에 등록할 수 있습니다.
Kotlin
@Throws(Exception::class) override fun setUp() { super.setUp() getActivity() setFailureHandler(CustomFailureHandler( ApplicationProvider.getApplicationContext<Context>())) }
Java
@Override public void setUp() throws Exception { super.setUp(); getActivity(); setFailureHandler(new CustomFailureHandler( ApplicationProvider.getApplicationContext())); }
자세한 내용은 FailureHandler
인터페이스 및
Espresso.setFailureHandler()
를 참고하세요.
기본이 아닌 창 타겟팅
Android는 여러 창을 지원합니다. 일반적으로 이는 사용자와 앱 개발자에게 투명하지만 자동 완성 창이 검색 위젯의 기본 애플리케이션 창 위에 그려지는 경우와 같은 특정 경우에는 여러 창이 표시됩니다. 작업을 단순화하기 위해 기본적으로 Espresso는 휴리스틱을 사용하여 상호작용할 Window
를 추측합니다. 이 휴리스틱은 거의 대부분의 경우에 충분하지만 드문 경우지만 상호작용이 타겟팅해야 하는 창을 지정해야 합니다. 자체 루트 창 매처 또는 Root
매처를 제공하여 실행할 수 있습니다.
Kotlin
onView(withText("South China Sea")) .inRoot(withDecorView(not(`is`(getActivity().getWindow().getDecorView())))) .perform(click())
Java
onView(withText("South China Sea")) .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))) .perform(click());
ViewMatchers
의 경우와 마찬가지로 사전 제공된 RootMatchers
집합이 제공됩니다.
물론, 언제든지 자체 Matcher
객체를 구현할 수 있습니다.
GitHub의 MultipleWindowTest 샘플을 살펴보세요.
목록 뷰에서 헤더 또는 바닥글 일치
addHeaderView()
및 addFooterView()
메서드를 사용하여 ListViews
에 머리글과 바닥글을 추가합니다. Espresso.onData()
에서 일치시킬 데이터 객체를 알 수 있도록 하려면 미리 설정된 데이터 객체 값을 두 번째 매개변수로 addHeaderView()
및 addFooterView()
에 전달해야 합니다. 예:
Kotlin
const val FOOTER = "FOOTER" ... val footerView = layoutInflater.inflate(R.layout.list_item, listView, false) footerView.findViewById<TextView>(R.id.item_content).text = "count:" footerView.findViewById<TextView>(R.id.item_size).text = data.size.toString listView.addFooterView(footerView, FOOTER, true)
Java
public static final String FOOTER = "FOOTER"; ... View footerView = layoutInflater.inflate(R.layout.list_item, listView, false); footerView.findViewById<TextView>(R.id.item_content).setText("count:"); footerView.findViewById<TextView>(R.id.item_size).setText(String.valueOf(data.size())); listView.addFooterView(footerView, FOOTER, true);
그런 다음 바닥글용 매처를 작성할 수 있습니다.
Kotlin
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.instanceOf import org.hamcrest.Matchers.`is` fun isFooter(): Matcher<Any> { return allOf(`is`(instanceOf(String::class.java)), `is`(LongListActivity.FOOTER)) }
Java
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @SuppressWarnings("unchecked") public static Matcher<Object> isFooter() { return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER)); }
테스트에서 뷰를 로드하는 것은 간단합니다.
Kotlin
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.sample.LongListMatchers.isFooter fun testClickFooter() { onData(isFooter()) .perform(click()) // ... }
Java
import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.sample.LongListMatchers.isFooter; public void testClickFooter() { onData(isFooter()) .perform(click()); // ... }
GitHub에서 AdapterViewTest.java
의 testClickFooter()
메서드에 있는 전체 코드 샘플을 살펴보세요.