Espresso: Thread.sleep ();


102

Espresso mengklaim bahwa tidak perlu Thread.sleep();, tetapi kode saya tidak berfungsi kecuali saya menyertakannya. Saya terhubung ke IP. Saat menghubungkan, dialog kemajuan ditampilkan. Saya perlu sleepmenunggu dialog ditutup. Ini adalah cuplikan pengujian saya di mana saya menggunakannya:

    IP.enterIP(); // fills out an IP dialog (this is done with espresso)

    //progress dialog is now shown
    Thread.sleep(1500);

    onView(withId(R.id.button).perform(click());

Saya telah mencoba kode ini dengan dan tanpa yang Thread.sleep();tetapi mengatakan R.id.Buttontidak ada. Satu-satunya cara agar saya berhasil adalah dengan tidur.

Juga, saya telah mencoba mengganti Thread.sleep();dengan hal-hal seperti getInstrumentation().waitForIdleSync();dan masih belum berhasil.

Apakah ini satu-satunya cara untuk melakukan ini? Atau apakah saya melewatkan sesuatu?

Terima kasih sebelumnya.


apakah mungkin bagi Anda untuk meletakkan loop While yang tidak diinginkan karena Anda ingin memblokir panggilan.
kedark

ok .. biar saya jelaskan. 2 saran untuk Anda 1st) Menerapkan sesuatu seperti jenis mekanisme panggilan-kembali. on-connection-membangun panggilan satu metode dan menunjukkan tampilan. 2) Anda ingin membuat penundaan di antara IP.enterIP (); dan onView (....) sehingga Anda dapat menempatkan loop sementara yang akan membuat jenis penundaan serupa untuk memanggil onview (..) ... tetapi saya rasa jika memungkinkan, pilih opsi No 1. (membuat panggilan-kembali mekanisme) ...
kedark

@kedark Ya itu pilihan, tapi apakah itu solusi Espresso?
Chad Bingham

Ada komentar yang belum terjawab dalam pertanyaan Anda, dapatkah Anda menjawabnya?
Bolhoso

@Bolhoso, pertanyaan apa?
Chad Bingham

Jawaban:


110

Menurut pendapat saya, pendekatan yang benar adalah:

/** Perform action of waiting for a specific view id. */
public static ViewAction waitId(final int viewId, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            uiController.loopMainThreadUntilIdle();
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + millis;
            final Matcher<View> viewMatcher = withId(viewId);

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    // found view with required ID
                    if (viewMatcher.matches(child)) {
                        return;
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}

Dan kemudian pola penggunaannya adalah:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));

3
Terima kasih Alex, mengapa Anda memilih opsi ini daripada IdlingResource atau AsyncTasks?
Tim Boland

1
Ini adalah pendekatan solusi, dalam banyak kasus Espresso melakukan pekerjaan tanpa masalah dan 'kode tunggu' khusus. Saya sebenarnya mencoba beberapa cara berbeda, dan menurut saya ini adalah cara yang paling cocok dengan arsitektur / desain Espresso.
Oleksandr Kucherenko

1
@AlexK ini membuat teman hari saya!
dawid gdanski

1
bagi saya, gagal untuk api <= 19, pada baris melempar PerformException.Builder () baru
Prabin Timsina

4
Saya harap Anda mengerti bahwa ini adalah contoh, Anda dapat menyalin / menempel dan memodifikasi untuk kebutuhan sendiri. Ini sepenuhnya menjadi tanggung jawab Anda untuk menggunakannya dengan benar dalam kebutuhan bisnis sendiri, bukan kebutuhan saya.
Oleksandr Kucherenko

47

Terima kasih kepada AlexK atas jawabannya yang luar biasa. Ada kasus di mana Anda perlu membuat penundaan dalam kode. Ini tidak selalu menunggu respons server tetapi mungkin menunggu animasi selesai. Saya pribadi memiliki masalah dengan Espresso idolingResources (saya pikir kami menulis banyak baris kode untuk hal yang sederhana) jadi saya mengubah cara AlexK melakukan menjadi kode berikut:

/**
 * Perform action of waiting for a specific time.
 */
public static ViewAction waitFor(final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "Wait for " + millis + " milliseconds.";
        }

        @Override
        public void perform(UiController uiController, final View view) {
            uiController.loopMainThreadForAtLeast(millis);
        }
    };
}

Jadi Anda dapat membuat Delaykelas dan memasukkan metode ini ke dalamnya untuk mengaksesnya dengan mudah. Anda dapat menggunakannya di kelas Tes Anda dengan cara yang sama:onView(isRoot()).perform(waitFor(5000));


7
metode perform bahkan dapat disederhanakan dengan satu baris seperti ini: uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka

Luar biasa, saya tidak tahu itu: thumbs_up @YairKukielka
Hesam

Astaga untuk menunggu sibuk.
TWiStErRob

Hebat. Saya mencari itu selama berabad-abad. 1 untuk solusi sederhana untuk masalah menunggu.
Tobias Reich

Cara yang jauh lebih baik untuk menambahkan penundaan daripada menggunakanThread.sleep()
Wahib Ul Haq

23

Saya menemukan utas ini ketika mencari jawaban untuk masalah serupa di mana saya sedang menunggu respons server dan mengubah visibilitas elemen berdasarkan respons.

Sementara solusi di atas benar-benar membantu, saya akhirnya menemukan contoh yang sangat baik ini dari chiuki dan sekarang menggunakan pendekatan itu sebagai tujuan saya setiap kali saya menunggu tindakan terjadi selama periode idle aplikasi.

Saya telah menambahkan ElapsedTimeIdlingResource () ke kelas utilitas saya sendiri, sekarang dapat secara efektif menggunakannya sebagai alternatif yang sesuai untuk Espresso, dan sekarang penggunaannya bagus dan bersih:

// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

// Now we wait
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

// Stop and verify
onView(withId(R.id.toggle_button))
    .check(matches(withText(R.string.stop)))
    .perform(click());
onView(withId(R.id.result))
    .check(matches(withText(success ? R.string.success: R.string.failure)));

// Clean up
Espresso.unregisterIdlingResources(idlingResource);

Saya mendapatkan I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourcekesalahan. Ada ide. Saya menggunakan Proguard tetapi dengan menonaktifkan obfuscation.
Anthony

Coba tambahkan -keeppernyataan untuk kelas yang tidak ditemukan untuk memastikan bahwa ProGuard tidak menghapusnya karena tidak perlu. Info lebih lanjut di sini: developer.android.com/tools/help/proguard.html#keep-code
MattMatt

Saya memposting pertanyaan stackoverflow.com/questions/36859528/… . Kelas ada di seed.txt dan mapping.txt
Anthony

2
Jika Anda perlu mengubah kebijakan pemalasan, Anda mungkin tidak mengimplementasikan sumber daya pemalasan dengan benar. Dalam jangka panjang, jauh lebih baik menginvestasikan waktu untuk memperbaikinya. Metode ini pada akhirnya akan menghasilkan tes yang lambat dan tidak stabil. Lihat google.github.io/android-testing-support-library/docs/espresso/…
Jose Alcérreca

Anda benar. Jawaban ini sudah berumur lebih dari satu tahun, dan sejak itu perilaku sumber daya yang menganggur telah meningkat sedemikian rupa sehingga kasus penggunaan yang sama yang saya gunakan untuk kode di atas sekarang berfungsi di luar kotak, mendeteksi dengan benar klien API yang diolok-olok - kami tidak lagi menggunakan yang di atas ElapsedTimeIdlingResource dalam pengujian berinstrumen kami karena alasan tersebut. (Tentu saja Anda juga bisa Rx semua hal, yang meniadakan kebutuhan untuk meretas dalam masa tunggu). Meski begitu, cara Google melakukan sesuatu tidak selalu yang terbaik: filosoficalhacker.com/post/… .
MattMatt

18

Saya pikir lebih mudah menambahkan baris ini:

SystemClock.sleep(1500);

Menunggu sejumlah milidetik (dari uptimeMillis) sebelum kembali. Mirip dengan sleep (long), tetapi tidak membuang InterruptedException; peristiwa interrupt () ditunda hingga operasi interupsi berikutnya. Tidak kembali hingga setidaknya jumlah milidetik yang ditentukan telah berlalu.


Expresso adalah untuk menghindari tidur hardcode yang menyebabkan tes tidak stabil. jika ini masalahnya, saya juga dapat menggunakan alat blackbox seperti appium
Emjey

6

Anda bisa menggunakan metode Barista:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista adalah pustaka yang membungkus Espresso untuk menghindari penambahan semua kode yang dibutuhkan oleh jawaban yang diterima. Dan ini tautannya! https://github.com/SchibstedSpain/Barista


Saya tidak mengerti perbedaan antara ini dan hanya melakukan thread sleep
Pablo Caviglia

Jujur, saya tidak ingat di video mana dari Google seorang pria mengatakan bahwa kita harus menggunakan cara ini untuk tidur daripada membuat umum Thread.sleep(). Maaf! Itu ada di beberapa video pertama yang dibuat Google tentang Espresso tapi saya tidak ingat yang mana ... beberapa tahun yang lalu. Maaf! : ·) Oh! Sunting! Saya memasang tautan ke video di PR yang saya buka tiga tahun lalu. Saksikan berikut ini! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

Ini mirip dengan jawaban ini tetapi menggunakan batas waktu sebagai ganti upaya dan dapat dihubungkan dengan ViewInteractions lainnya:

/**
 * Wait for view to be visible
 */
fun ViewInteraction.waitUntilVisible(timeout: Long): ViewInteraction {
    val startTime = System.currentTimeMillis()
    val endTime = startTime + timeout

    do {
        try {
            check(matches(isDisplayed()))
            return this
        } catch (e: NoMatchingViewException) {
            Thread.sleep(50)
        }
    } while (System.currentTimeMillis() < endTime)

    throw TimeoutException()
}

Pemakaian:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())

4

Saya baru mengenal coding dan Espresso, jadi meskipun saya tahu solusi yang baik dan masuk akal adalah menggunakan idling, saya belum cukup cerdas untuk melakukan itu.

Sampai saya menjadi lebih berpengetahuan, saya masih membutuhkan tes saya untuk berjalan, jadi untuk saat ini saya menggunakan solusi kotor ini yang membuat sejumlah upaya untuk menemukan elemen, berhenti jika menemukannya dan jika tidak, sebentar tidur dan mulai lagi hingga mencapai jumlah percobaan maksimum (jumlah percobaan tertinggi sejauh ini sekitar 150).

private static boolean waitForElementUntilDisplayed(ViewInteraction element) {
    int i = 0;
    while (i++ < ATTEMPTS) {
        try {
            element.check(matches(isDisplayed()));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Thread.sleep(WAITING_TIME);
            } catch (Exception e1) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

Saya menggunakan ini di semua metode yang menemukan elemen berdasarkan ID, teks, induk dll:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}

dalam contoh Anda, findById(int itemId)metode ini akan mengembalikan elemen (yang bisa jadi NULL) apakah waitForElementUntilDisplayed(element);mengembalikan benar atau salah .... jadi, itu tidak baik
mbob

Hanya ingin berpadu dan mengatakan ini adalah solusi terbaik menurut saya. IdlingResourceIni tidak cukup bagi saya karena perincian tingkat polling 5 detik (terlalu besar untuk kasus penggunaan saya). Jawaban yang diterima juga tidak berfungsi untuk saya (penjelasan mengapa sudah termasuk dalam umpan komentar panjang jawaban itu). Terima kasih untuk ini! Saya mengambil ide Anda dan membuat solusi saya sendiri dan itu bekerja seperti pesona.
oaskamay

Ya, ini adalah satu-satunya solusi yang berhasil untuk saya juga, ketika ingin menunggu elemen yang tidak ada dalam aktivitas saat ini.
guilhermekrz

3

Espresso dibuat untuk menghindari panggilan sleep () dalam pengujian. Pengujian Anda seharusnya tidak membuka dialog untuk memasukkan IP, yang seharusnya menjadi tanggung jawab aktivitas yang diuji.

Di sisi lain, pengujian UI Anda harus:

  • Tunggu hingga dialog IP muncul
  • Isi alamat IP dan klik enter
  • Tunggu hingga tombol Anda muncul dan klik

Tesnya akan terlihat seperti ini:

// type the IP and press OK
onView (withId (R.id.dialog_ip_edit_text))
  .check (matches(isDisplayed()))
  .perform (typeText("IP-TO-BE-TYPED"));

onView (withText (R.string.dialog_ok_button_title))
  .check (matches(isDisplayed()))
  .perform (click());

// now, wait for the button and click it
onView (withId (R.id.button))
  .check (matches(isDisplayed()))
  .perform (click());

Espresso menunggu semua yang terjadi di UI thread dan kumpulan AsyncTask selesai sebelum menjalankan pengujian Anda.

Ingatlah bahwa pengujian Anda tidak boleh melakukan apa pun yang merupakan kemampuan aplikasi Anda. Ini harus berperilaku seperti "pengguna yang terinformasi dengan baik": pengguna yang mengklik, memverifikasi bahwa ada sesuatu yang ditampilkan di layar, tetapi, pada kenyataannya, mengetahui ID dari komponen


2
Kode contoh Anda pada dasarnya adalah kode yang sama yang saya tulis dalam pertanyaan saya.
Chad Bingham

@Binghammer yang saya maksud adalah tes harus berperilaku seperti pengguna berperilaku. Mungkin poin yang saya lewatkan adalah apa yang dilakukan metode IP.enterIP () Anda. Dapatkah Anda mengedit pertanyaan Anda dan menjelaskannya?
Bolhoso

Komentar saya mengatakan apa yang dilakukannya. Ini hanyalah sebuah metode di espresso yang mengisi dialog IP. Itu semua UI.
Chad Bingham

mm ... ok, jadi Anda benar, tes saya pada dasarnya melakukan hal yang sama. Apakah Anda melakukan sesuatu di luar thread UI atau AsyncTasks?
Bolhoso

16
Espresso tidak berfungsi seperti kode dan teks dari jawaban ini sepertinya menyiratkan. Panggilan cek pada ViewInteraction tidak akan menunggu sampai Matcher yang diberikan berhasil, tetapi langsung gagal jika kondisi tidak terpenuhi. Cara yang tepat untuk melakukannya adalah dengan menggunakan AsyncTasks, seperti yang disebutkan dalam jawaban ini, atau, jika tidak memungkinkan, terapkan IdlingResource yang akan memberi tahu UiController Espresso pada saat OK untuk melanjutkan eksekusi pengujian.
haffax

2

Anda harus menggunakan Espresso Idling Resource, yang disarankan di CodeLab ini

Resource nonaktif merepresentasikan operasi asinkron yang hasilnya memengaruhi operasi selanjutnya dalam pengujian UI. Dengan mendaftarkan resource nonaktif dengan Espresso, Anda dapat memvalidasi operasi asinkron ini dengan lebih andal saat menguji aplikasi Anda.

Contoh panggilan Asynchronous dari Presenter

@Override
public void loadNotes(boolean forceUpdate) {
   mNotesView.setProgressIndicator(true);
   if (forceUpdate) {
       mNotesRepository.refreshData();
   }

   // The network request might be handled in a different thread so make sure Espresso knows
   // that the app is busy until the response is handled.
   EspressoIdlingResource.increment(); // App is busy until further notice

   mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
       @Override
       public void onNotesLoaded(List<Note> notes) {
           EspressoIdlingResource.decrement(); // Set app as idle.
           mNotesView.setProgressIndicator(false);
           mNotesView.showNotes(notes);
       }
   });
}

Dependensi

androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'

Untuk androidx

androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.2'

Repo Resmi: https://github.com/googlecodelabs/android-testing

Contoh IdlingResource: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample


0

Meskipun menurut saya yang terbaik adalah menggunakan Idling Resources untuk ini ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ) Anda mungkin dapat menggunakan ini sebagai fallback:

/**
 * Contains view interactions, view actions and view assertions which allow to set a timeout
 * for finding a view and performing an action/view assertion on it.
 * To be used instead of {@link Espresso}'s methods.
 * 
 * @author Piotr Zawadzki
 */
public class TimeoutEspresso {

    private static final int SLEEP_IN_A_LOOP_TIME = 50;

    private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10 * 1000L;

    /**
     * Use instead of {@link Espresso#onView(Matcher)}
     * @param timeoutInMillis timeout after which an error is thrown
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(long timeoutInMillis, @NonNull final Matcher<View> viewMatcher) {

        final long startTime = System.currentTimeMillis();
        final long endTime = startTime + timeoutInMillis;

        do {
            try {
                return new TimedViewInteraction(Espresso.onView(viewMatcher));
            } catch (NoMatchingViewException ex) {
                //ignore
            }

            SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
        }
        while (System.currentTimeMillis() < endTime);

        // timeout happens
        throw new PerformException.Builder()
                .withCause(new TimeoutException("Timeout occurred when trying to find: " + viewMatcher.toString()))
                .build();
    }

    /**
     * Use instead of {@link Espresso#onView(Matcher)}.
     * Same as {@link #onViewWithTimeout(long, Matcher)} but with the default timeout {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(@NonNull final Matcher<View> viewMatcher) {
        return onViewWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewMatcher);
    }

    /**
     * A wrapper around {@link ViewInteraction} which allows to set timeouts for view actions and assertions.
     */
    public static class TimedViewInteraction {

        private ViewInteraction wrappedViewInteraction;

        public TimedViewInteraction(ViewInteraction wrappedViewInteraction) {
            this.wrappedViewInteraction = wrappedViewInteraction;
        }

        /**
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction perform(final ViewAction... viewActions) {
            wrappedViewInteraction.perform(viewActions);
            return this;
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(final ViewAction... viewActions) {
            return performWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewActions);
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(long timeoutInMillis, final ViewAction... viewActions) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return perform(viewActions);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to perform view actions: " + viewActions))
                    .build();
        }

        /**
         * @see ViewInteraction#withFailureHandler(FailureHandler)
         */
        public TimedViewInteraction withFailureHandler(FailureHandler failureHandler) {
            wrappedViewInteraction.withFailureHandler(failureHandler);
            return this;
        }

        /**
         * @see ViewInteraction#inRoot(Matcher)
         */
        public TimedViewInteraction inRoot(Matcher<Root> rootMatcher) {
            wrappedViewInteraction.inRoot(rootMatcher);
            return this;
        }

        /**
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction check(final ViewAssertion viewAssert) {
            wrappedViewInteraction.check(viewAssert);
            return this;
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(final ViewAssertion viewAssert) {
            return checkWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewAssert);
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(long timeoutInMillis, final ViewAssertion viewAssert) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return check(viewAssert);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to check: " + viewAssert.toString()))
                    .build();
        }
    }
}

dan kemudian menyebutnya dalam kode Anda sebagai contoh:

onViewWithTimeout(withId(R.id.button).perform(click());

dari pada

onView(withId(R.id.button).perform(click());

Ini juga memungkinkan Anda menambahkan waktu tunggu untuk tindakan tampilan dan pernyataan tampilan.


Gunakan satu baris kode di bawah ini untuk menangani kasus uji Test Espresso apa pun: SystemClock.sleep (1000); // 1 Detik
Nikunjkumar Kapupara

bagi saya ini hanya bekerja dengan mengubah baris ini return new TimedViewInteraction(Espresso.onView(viewMatcher));denganreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger

0

Utilitas saya mengulangi eksekusi runnable atau callable hingga lolos tanpa kesalahan atau melempar setelah batas waktu. Ini bekerja dengan sempurna untuk tes Espresso!

Misalkan interaksi tampilan terakhir (klik tombol) mengaktifkan beberapa utas latar belakang (jaringan, database, dll.). Hasilnya, layar baru akan muncul dan kami ingin memeriksanya di langkah berikutnya, tetapi kami tidak tahu kapan layar baru akan siap untuk diuji.

Pendekatan yang direkomendasikan adalah memaksa aplikasi Anda mengirim pesan tentang status thread ke pengujian Anda. Terkadang kita dapat menggunakan mekanisme bawaan seperti OkHttp3IdlingResource. Dalam kasus lain, Anda harus menyisipkan potongan kode di berbagai tempat sumber aplikasi Anda (Anda harus mengetahui logika aplikasi!) Untuk menguji dukungan saja. Selain itu, kami harus mematikan semua animasi Anda (meskipun itu bagian dari UI).

Pendekatan lainnya sedang menunggu, misalnya SystemClock.sleep (10000). Tetapi kami tidak tahu berapa lama harus menunggu dan bahkan penundaan yang lama tidak dapat menjamin kesuksesan. Di sisi lain, tes Anda akan bertahan lama.

Pendekatan saya adalah menambahkan kondisi waktu untuk melihat interaksi. Misalnya kami menguji bahwa layar baru akan muncul selama 10000 mc (batas waktu). Tetapi kami tidak menunggu dan memeriksanya secepat yang kami inginkan (mis. Setiap 100 md) Tentu saja, kami memblokir utas pengujian seperti itu, tetapi biasanya, itu hanya yang kami butuhkan dalam kasus seperti itu.

Usage:

long timeout=10000;
long matchDelay=100; //(check every 100 ms)
EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);

ViewInteraction loginButton = onView(withId(R.id.login_btn));
loginButton.perform(click());

myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));

Ini adalah sumber kelas saya:

/**
 * Created by alexshr on 02.05.2017.
 */

package com.skb.goodsapp;

import android.os.SystemClock;
import android.util.Log;

import java.util.Date;
import java.util.concurrent.Callable;

/**
 * The utility repeats runnable or callable executing until it pass without errors or throws throwable after timeout.
 * It works perfectly for Espresso tests.
 * <p>
 * Suppose the last view interaction (button click) activates some background threads (network, database etc.).
 * As the result new screen should appear and we want to check it in our next step,
 * but we don't know when new screen will be ready to be tested.
 * <p>
 * Recommended approach is to force your app to send messages about threads states to your test.
 * Sometimes we can use built-in mechanisms like OkHttp3IdlingResource.
 * In other cases you should insert code pieces in different places of your app sources (you should known app logic!) for testing support only.
 * Moreover, we should turn off all your animations (although it's the part on ui).
 * <p>
 * The other approach is waiting, e.g. SystemClock.sleep(10000). But we don't known how long to wait and even long delays can't guarantee success.
 * On the other hand your test will last long.
 * <p>
 * My approach is to add time condition to view interaction. E.g. we test that new screen should appear during 10000 mc (timeout).
 * But we don't wait and check new screen as quickly as it appears.
 * Of course, we block test thread such way, but usually it's just what we need in such cases.
 * <p>
 * Usage:
 * <p>
 * long timeout=10000;
 * long matchDelay=100; //(check every 100 ms)
 * EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);
 * <p>
 * ViewInteraction loginButton = onView(withId(R.id.login_btn));
 * loginButton.perform(click());
 * <p>
 * myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));
 */
public class EspressoExecutor<T> {

    private static String LOG = EspressoExecutor.class.getSimpleName();

    public static long REPEAT_DELAY_DEFAULT = 100;
    public static long BEFORE_DELAY_DEFAULT = 0;

    private long mRepeatDelay;//delay between attempts
    private long mBeforeDelay;//to start attempts after this initial delay only

    private long mTimeout;//timeout for view interaction

    private T mResult;

    /**
     * @param timeout     timeout for view interaction
     * @param repeatDelay - delay between executing attempts
     * @param beforeDelay - to start executing attempts after this delay only
     */

    public EspressoExecutor(long timeout, long repeatDelay, long beforeDelay) {
        mRepeatDelay = repeatDelay;
        mBeforeDelay = beforeDelay;
        mTimeout = timeout;
        Log.d(LOG, "created timeout=" + timeout + " repeatDelay=" + repeatDelay + " beforeDelay=" + beforeDelay);
    }

    public EspressoExecutor(long timeout, long repeatDelay) {
        this(timeout, repeatDelay, BEFORE_DELAY_DEFAULT);
    }

    public EspressoExecutor(long timeout) {
        this(timeout, REPEAT_DELAY_DEFAULT);
    }


    /**
     * call with result
     *
     * @param callable
     * @return callable result
     * or throws RuntimeException (test failure)
     */
    public T call(Callable<T> callable) {
        call(callable, null);
        return mResult;
    }

    /**
     * call without result
     *
     * @param runnable
     * @return void
     * or throws RuntimeException (test failure)
     */
    public void call(Runnable runnable) {
        call(runnable, null);
    }

    private void call(Object obj, Long initialTime) {
        try {
            if (initialTime == null) {
                initialTime = new Date().getTime();
                Log.d(LOG, "sleep delay= " + mBeforeDelay);
                SystemClock.sleep(mBeforeDelay);
            }

            if (obj instanceof Callable) {
                Log.d(LOG, "call callable");
                mResult = ((Callable<T>) obj).call();
            } else {
                Log.d(LOG, "call runnable");
                ((Runnable) obj).run();
            }
        } catch (Throwable e) {
            long remain = new Date().getTime() - initialTime;
            Log.d(LOG, "remain time= " + remain);
            if (remain > mTimeout) {
                throw new RuntimeException(e);
            } else {
                Log.d(LOG, "sleep delay= " + mRepeatDelay);
                SystemClock.sleep(mRepeatDelay);
                call(obj, initialTime);
            }
        }
    }
}

https://gist.github.com/alexshr/ca90212e49e74eb201fbc976255b47e0


0

Ini adalah helper yang saya gunakan di Kotlin untuk Pengujian Android. Dalam kasus saya, saya menggunakan longOperation untuk meniru respons server tetapi Anda dapat menyesuaikannya untuk tujuan Anda.

@Test
fun ensureItemDetailIsCalledForRowClicked() {
    onView(withId(R.id.input_text))
        .perform(ViewActions.typeText(""), ViewActions.closeSoftKeyboard())
    onView(withId(R.id.search_icon)).perform(ViewActions.click())
    longOperation(
        longOperation = { Thread.sleep(1000) },
        callback = {onView(withId(R.id.result_list)).check(isVisible())})
}

private fun longOperation(
    longOperation: ()-> Unit,
    callback: ()-> Unit
){
    Thread{
        longOperation()
        callback()
    }.start()
}

0

Saya akan menambahkan cara saya melakukan ini ke dalam campuran:

fun suspendUntilSuccess(actionToSucceed: () -> Unit, iteration : Int = 0) {
    try {
        actionToSucceed.invoke()
    } catch (e: Throwable) {
        Thread.sleep(200)
        val incrementedIteration : Int = iteration + 1
        if (incrementedIteration == 25) {
            fail("Failed after waiting for action to succeed for 5 seconds.")
        }
        suspendUntilSuccess(actionToSucceed, incrementedIteration)
    }
}

Disebut seperti ini:

suspendUntilSuccess({
    checkThat.viewIsVisible(R.id.textView)
})

Anda dapat menambahkan parameter seperti iterasi maks, panjang iterasi, dll ke fungsi suspendUntilSuccess.

Saya masih lebih suka menggunakan sumber daya yang tidak aktif, tetapi ketika pengujian dilakukan karena animasi yang lambat pada perangkat misalnya, saya menggunakan fungsi ini dan berfungsi dengan baik. Ini tentu saja dapat bertahan hingga 5 detik seperti sebelum gagal, sehingga dapat meningkatkan waktu eksekusi pengujian Anda jika tindakan untuk berhasil tidak pernah berhasil.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.