이 문서에서는 다양한 일반 Espresso 테스트를 설정하는 방법을 설명합니다.
뷰를 옆의 다른 뷰와 일치
레이아웃에 그 자체로 고유하지 않은 특정 뷰가 포함될 수 있습니다. 예를 들어 연락처 테이블의 반복 통화 버튼에 뷰 계층 구조 내의 다른 통화 버튼과 동일한 R.id
, 텍스트 및 속성이 있을 수 있습니다.
예를 들어 이 활동에서는 "7"
이라는 텍스트가 있는 뷰가 여러 행에서 반복됩니다.
고유하지 않은 뷰가 옆에 있는 고유한 일부 라벨과 쌍을 이루는 경우가 흔히 있습니다(예: 통화 버튼 옆에 있는 연락처 이름). 이 경우 다음과 같이 hasSibling()
매처를 사용하여 선택 범위를 좁힐 수 있습니다.
Kotlin
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
자바
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"))) }
자바
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"))) }
자바
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"))) }
자바
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"))) } }
자바
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())))
자바
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())
자바
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 } } }
자바
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"))))) } }
자바
@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) } } }
자바
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
에 위임합니다. 다음과 같이 테스트의 setUp()
메서드에서 CustomFailureHandler
를 Espresso에 등록할 수 있습니다.
Kotlin
@Throws(Exception::class) override fun setUp() { super.setUp() getActivity() setFailureHandler(CustomFailureHandler( ApplicationProvider.getApplicationContext<Context>())) }
자바
@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())
자바
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)
자바
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)) }
자바
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()) // ... }
자바
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()
메서드에 있는 전체 코드 샘플을 살펴보세요.