Bu belgede, yaygın olarak kullanılan çeşitli Espresso testlerinin nasıl oluşturulacağı açıklanmaktadır.
Başka bir görünümün yanındaki görünümle eşleştirme
Bir düzen, tek başına benzersiz olmayan belirli görünümleri içerebilir. Örneğin, kişiler tablosunda tekrarlanan bir arama düğmesi aynı R.id
değerine sahip olabilir, aynı metni içerebilir ve görünüm hiyerarşisindeki diğer çağrı düğmeleriyle aynı özelliklere sahip olabilir.
Örneğin, bu etkinlikte "7"
metnini içeren görünüm birden fazla satırda tekrarlanır:
Benzersiz olmayan görünüm genellikle yanında bulunan bazı benzersiz etiketle (örneğin, çağrı düğmesinin yanındaki kişinin adı) eşleştirilir. Bu durumda, hasSibling()
eşleştiriciyi kullanarak seçiminizi daraltabilirsiniz:
Kotlin
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
Java
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click());
İşlem çubuğunun içindeki bir görünümü eşleştirme
ActionBarTestActivity
iki farklı işlem çubuğuna sahiptir: normal bir işlem çubuğu ve seçenekler menüsünden oluşturulan içeriğe dayalı işlem çubuğu. Her iki işlem çubuğunda da her zaman görünür olan bir öğe ve yalnızca taşma menüsünde görünen iki öğe bulunur. Bir öğe tıklandığında, tıklanan öğenin içeriğiyle bir TextView değiştirilir.
Aşağıdaki kod snippet'inde gösterildiği gibi, her iki işlem çubuğunda da görünür simgeleri eşleştirmek oldukça basittir:
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"))); }
Kod, bağlamsal işlem çubuğu için aynı görünür:
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"))); }
Bazı cihazlarda seçenekler menüsündeki taşan öğeleri açan bir donanım taşma menüsü düğmesi, bazı cihazlarda ise normal bir taşma menüsünü açan bir yazılım taşma menüsü düğmesi bulunur. Bu nedenle, taşma menüsündeki öğelere tıklamak, normal işlem çubuğu için biraz zordur. Neyse ki Espresso bunu bizim için hallediyor.
Normal işlem çubuğu için:
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"))); }
Bu, donanım taşma menüsü düğmesi olan cihazlarda şu şekilde görünür:
Bağlamsal işlem çubuğu için bu çok kolaydır:
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"))); } }
Bu örneklerdeki kodun tamamını görmek için GitHub'daki ActionBarTest.java
örneğini görüntüleyin.
Bir görünümün görüntülenmediğini iddia edin
Bir dizi işlem yaptıktan sonra, test edilen kullanıcı arayüzünün durumunu kesinlikle doğrulamak isteyebilirsiniz. Bu bazen, mesela bir şey olmaması gibi
olumsuz bir durum olabilir. ViewAssertions.matches()
kullanarak herhangi bir hamcrest görünüm eşleştiriciyi ViewAssertion
öğesine dönüştürebileceğinizi unutmayın.
Aşağıdaki örnekte, isDisplayed()
eşleştiriciyi alıp standart not()
eşleştiriciyi kullanarak tersine çeviriyoruz:
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())));
Yukarıdaki yaklaşım, görünüm hâlâ hiyerarşinin bir parçasıysa işe yarar. Aksi takdirde bir NoMatchingViewException
alırsınız ve ViewAssertions.doesNotExist()
kullanmanız gerekir.
Bir görünümün mevcut olmadığını iddia etme
Görünüm, görünüm hiyerarşisinden kaldırıldıysa (bir işlem başka bir etkinliğe geçişe neden olduğunda olabilir) ViewAssertions.doesNotExist()
kullanmanız gerekir:
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());
Bir veri öğesinin bağdaştırıcıda olmadığını iddia edin
Belirli bir veri öğesinin AdapterView
dahilinde olmadığını kanıtlamak için işlemleri biraz farklı şekilde yapmanız gerekir. İlgilendiğimiz AdapterView
öğesini bulmalı ve
sahip olduğu verileri sorgulamalıyız. onData()
kullanmanız gerekmez.
Bunun yerine, AdapterView
öğesini bulmak için onView()
ve ardından görünümdeki veriler üzerinde çalışmak için başka bir eşleştirici kullanırız.
Önce eşleştiren:
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; } }; }
Öyleyse AdapterView
öğesini bulmak için yalnızca onView()
yeterli:
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"))))); } }
"item: 168"e eşit bir öğe, kimlik listesiyle bağdaştırıcı görünümünde yer alıyorsa başarısız olacak bir iddiamız vardır.
Örneğin tamamı için GitHub'daki AdapterViewTest.java
sınıfında yer alan testDataItemNotInAdapter()
yöntemine bakın.
Özel hata işleyici kullan
Espresso'daki varsayılan FailureHandler
değerinin özel bir öğeyle değiştirilmesi, ekran görüntüsü alma veya ekstra hata ayıklama bilgilerini iletme gibi ek veya farklı bir hata kontrolüne olanak tanır.
CustomFailureHandlerTest
örneğinde, özel bir hata işleyicinin nasıl uygulanacağı gösterilmektedir:
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); } } }
Bu hata işleyici, NoMatchingViewException
yerine bir MySpecialException
atar ve diğer tüm hataları DefaultFailureHandler
'a yetkilendirir. CustomFailureHandler
, testin setUp()
yönteminde Espresso'ya kaydedilebilir:
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())); }
Daha fazla bilgi için FailureHandler
arayüzünü ve
Espresso.setFailureHandler()
sayfasını inceleyin.
Varsayılan olmayan aralıkları hedefle
Android birden çok pencereyi destekler. Normalde bu çalışma kullanıcılar ve uygulama geliştirici için şeffaftır. Ancak arama widget'ındaki ana uygulama penceresinin üzerine bir otomatik tamamlama penceresinin çizilmesi gibi bazı durumlarda birden çok pencere görünür. Espresso, işlemleri basitleştirmek için hangi Window
ile etkileşime girmek istediğinizi tahmin etmek üzere varsayılan olarak buluşsal bir yöntem kullanır. Bu sezgisel yöntem neredeyse her zaman yeterince iyidir, ancak nadir durumlarda, bir etkileşimin hangi pencereyi hedeflemesi gerektiğini belirtmeniz gerekir. Bunu, kendi root pencere eşleyicinizi veya Root
eşleştiricinizi sağlayarak yapabilirsiniz:
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
'te olduğu gibi, önceden sağlanan bir RootMatchers
grubu sunuyoruz.
Elbette, kendi Matcher
nesnenizi her zaman uygulayabilirsiniz.
GitHub'da MultipleWindowTest örneğine göz atın.
Liste görünümünde üstbilgi veya altbilgiyi eşleştirme
Üstbilgiler ve altbilgiler, addHeaderView()
ve addFooterView()
yöntemleri kullanılarak ListViews
öğesine eklenir. Espresso.onData()
ürününün hangi veri nesnesinin eşleştirileceğini bildiğinden emin olmak için addHeaderView()
ve addFooterView()
özelliklerine ikinci parametre olarak, önceden ayarlanmış bir veri nesnesi değeri ilettiğinizden emin olun. Örneğin:
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);
Ardından, altbilgi için bir eşleştirici yazabilirsiniz:
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)); }
Görünümün testte yüklenmesi de son derece basittir:
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'da AdapterViewTest.java
için testClickFooter()
yönteminde bulunan tam kod örneğine göz atın.