Espresso tarifleri

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:

3 öğeli bir listede aynı görünüm öğesinin 3 kopyasını gösteren liste etkinliği

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

Kaydet düğmesi, etkinlik çubuğunda, etkinliğin üst tarafındadır

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

Kilit düğmesi, işlem çubuğunda etkinliğin üstündeki düğmedir

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

Taşma menüsü düğmesi görünür durumda ve ekranın üst kısmına yakın bir yerde bulunan işlem çubuğunun altında bir liste görünür.

Bu, donanım taşma menüsü düğmesi olan cihazlarda şu şekilde görünür:

Taşma menüsü düğmesi yoktur ve ekranın alt kısmına yakın bir yerde bir liste görüntülenir

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

Taşma menüsü düğmesi, işlem çubuğunda görünür. Seçenek listesi ise ekranın üst kısmındaki işlem çubuğunun altında görünür.

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.

Ü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.