Espresso: Thread.sleep ();


102

Espresso, buna gerek olmadığını iddia ediyor Thread.sleep();, ancak kodum eklemedikçe çalışmıyor. Bir IP'ye bağlanıyorum. Bağlanırken bir ilerleme diyaloğu gösterilir. sleepİletişim kutusunun kapanmasını beklemem gerekiyor . Bu, kullandığım test snippet'im:

    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());

Bu kodu denedim ile ve olmadanThread.sleep(); ama diyor R.id.Buttonyok. Onu çalıştırabilmemin tek yolu uyumaktır.

Ayrıca, Thread.sleep();gibi şeylerle değiştirmeyi denedim getInstrumentation().waitForIdleSync();ve hala şansım yok.

Bunu yapmanın tek yolu bu mu? Yoksa bir şey mi kaçırıyorum?

Şimdiden teşekkürler.


Yine de istenmeyen While döngüsünü koymanız mümkün mü, aramayı engellemek istiyorsunuz.
kedark

tamam .. açıklamama izin ver. Sizin için 2 öneri 1.) Geri arama mekanizması türü gibi bir şey uygulayın. on-connection-install bir yöntemi arayın ve görünümü gösterin. 2) IP.enterIP () arasında gecikme yaratmak istiyorsanız; ve onView (....) böylece onview (..) 'i çağırmak için benzer türde bir gecikme yaratacak while döngüsünü koyabilirsiniz ... ama eğer mümkünse lütfen 1. seçeneği tercih edin (geri arama oluşturma mekanizma) ...
kedark

@kedark Evet bu bir seçenek ama Espresso'nun çözümü bu mu?
Chad Bingham

Sorunuzda cevaplanmamış yorum var, cevap verebilir misiniz?
Bolhoso

@Bolhoso, ne soru?
Chad Bingham

Yanıtlar:


111

Aklımda doğru yaklaşım şöyle olacak:

/** 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();
        }
    };
}

Ve sonra kullanım şekli şöyle olacaktır:

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

3
Teşekkürler Alex, neden IdlingResource veya AsyncTasks yerine bu seçeneği seçtiniz?
Tim Boland

1
Bu geçici çözüm yaklaşımıdır, çoğu durumda Espresso işi sorunsuz ve özel 'bekleme kodu' olmadan yapar. Aslında birkaç farklı yol deniyorum ve bunun en uyumlu Espresso mimarisi / tasarımı olduğunu düşünüyorum.
Oleksandr Kucherenko

1
@AlexK bu benim günümü dostum yaptı!
dawid gdanski

1
benim için, api <= 19 için başarısız oluyor, satır atarken yeni PerformException.Builder ()
Prabin Timsina

4
Umarım bunun bir örnek olduğunu anlarsınız, kendi ihtiyaçlarınıza göre kopyalayıp yapıştırabilir ve değiştirebilirsiniz. Benim değil, kendi iş ihtiyaçlarınızda doğru şekilde kullanmak tamamen sizin sorumluluğunuzdadır.
Oleksandr Kucherenko

48

Harika cevabı için AlexK'ya teşekkürler. Kodda biraz gecikme yapmanız gereken durumlar vardır. Sunucu yanıtını beklemek zorunda değildir, ancak animasyonun tamamlanmasını bekliyor olabilir. Şahsen Espresso idolingResources ile ilgili problemim var (sanırım basit bir şey için birçok satır kod yazıyoruz), bu yüzden AlexK'nın aşağıdaki kodu yapma şeklini değiştirdim:

/**
 * 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);
        }
    };
}

Böylece bir Delaysınıf oluşturabilir ve ona kolayca erişmek için bu yöntemi içine koyabilirsiniz. Bunu Test sınıfınızda aynı şekilde kullanabilirsiniz:onView(isRoot()).perform(waitFor(5000));


7
perform yöntemi şu şekilde bir satırla basitleştirilebilir: uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka

Harika, bunu bilmiyordum: thumbs_up @YairKukielka
Hesam

Meşgul beklemek için tuhaf.
TWiStErRob

Harika. Bunu çağlar boyunca arıyordum. Bekleme problemlerine basit bir çözüm için +1.
Tobias Reich

Kullanmak yerine gecikme eklemenin çok daha iyi bir yoluThread.sleep()
Wahib Ul Haq

23

Bir sunucu yanıtı beklediğim ve yanıta göre öğelerin görünürlüğünü değiştirdiğim benzer bir soruna yanıt ararken bu konuya rastladım.

Yukarıdaki çözüm kesinlikle yardımcı olsa da, sonunda chiuki'den bu mükemmel örneği buldum ve şimdi bu yaklaşımı, uygulamanın boşta kaldığı dönemlerde eylemlerin gerçekleşmesini beklediğimde gitmem için kullanıyorum.

ElapsedTimeIdlingResource () 'u kendi yardımcı programlar sınıfıma ekledim , şimdi bunu Espresso'ya uygun bir alternatif olarak etkili bir şekilde kullanabilir ve şimdi kullanım güzel ve temiz:

// 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);

Bir I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourcehata alıyorum. Herhangi bir fikir. Proguard kullanıyorum ama gizlemeyi devre dışı bırakarak.
Anthony

-keepProGuard'ın gereksiz olarak kaldırmadığından emin olmak için bulunmayan sınıflar için bir ifade eklemeyi deneyin . Daha fazla bilgi burada: developer.android.com/tools/help/proguard.html#keep-code
MattMatt

Bir soru stackoverflow.com/questions/36859528/… gönderiyorum . Sınıf seed.txt ve mapping.txt dosyasında
Anthony,

2
Boşta çalışma politikalarını değiştirmeniz gerekirse, muhtemelen boşta kalan kaynakları doğru şekilde uygulamıyorsunuzdur. Uzun vadede, bunu düzeltmek için zaman ayırmak çok daha iyidir. Bu yöntem sonunda yavaş ve kesintili testlere yol açacaktır. Check out google.github.io/android-testing-support-library/docs/espresso/...
Jose Alcerreca

Oldukça haklısın. Bu cevap bir yıldan fazla bir süredir ve o zamandan beri boşta olan kaynakların davranışı öyle gelişti ki, şimdilik yukarıdaki kodu kullandığım aynı kullanım durumu, alay edilen API istemcisini doğru bir şekilde tespit ederek kutudan çıkar - artık yukarıdakileri kullanmıyoruz Bu nedenle, enstrümantasyonlu testlerimizde ElapsedTimeIdlingResource. (Elbette her şeyi Rx yapabilirsiniz, bu da bekleme süresinde hackleme ihtiyacını ortadan kaldırır). Bununla birlikte, Google'ın işleri yapma yöntemi her zaman en iyisi değildir: felsefihacker.com/post/… .
MattMatt

18

Bu satırı eklemenin daha kolay olduğunu düşünüyorum:

SystemClock.sleep(1500);

Dönmeden önce belirli bir milisaniye (uptimeMillis) kadar bekler. Uykuya benzer (uzun), ancak InterruptedException oluşturmaz; interrupt () olayları bir sonraki kesilebilir işleme kadar ertelenir. En azından belirtilen milisaniye sayısı geçene kadar geri dönmez.


Expresso, kesintili testlere neden olan bu kodlanmış uykudan kaçınmaktır. Eğer durum buysa
appium

6

Barista yöntemlerini kullanabilirsiniz:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista, kabul edilen cevabın ihtiyaç duyduğu tüm kodu eklemekten kaçınmak için Espresso'yu saran bir kütüphanedir. Ve işte bir bağlantı! https://github.com/SchibstedSpain/Barista


Bunun ile sadece iplik uykusu arasındaki farkı anlamıyorum
Pablo Caviglia

Dürüst olmak gerekirse, Google'dan hangi videoda bir adamın ortak yapmak yerine bu şekilde uyumak için kullanmamız gerektiğini söylediğini hatırlamıyorum Thread.sleep(). Afedersiniz! Google'ın Espresso ile ilgili yaptığı ilk videoların bazılarında vardı ama hangisi olduğunu hatırlamıyorum ... birkaç yıl önceydi. Afedersiniz! : ·) Ah! Düzenle! Üç yıl önce açtığım PR'de videonun bağlantısını koydum. Bunu kontrol et! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

Bu, bu yanıta benzer, ancak denemeler yerine bir zaman aşımı kullanır ve diğer ViewInteractions ile zincirlenebilir:

/**
 * 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()
}

Kullanım:

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

4

Kodlama ve Espresso konusunda yeniyim, bu yüzden iyi ve makul çözümün rölanti kullanmak olduğunu bilsem de henüz bunu yapacak kadar zeki değilim.

Daha bilgili hale gelene kadar, yine de bir şekilde çalışması için testlerime ihtiyacım var, bu yüzden şimdilik bir element bulmak için bir dizi girişimde bulunan, bulduğunda duran ve yoksa kısa bir süre uyuyup başlayan bu kirli çözümü kullanıyorum. tekrar maksimum deneme sayısına ulaşana kadar (şimdiye kadarki en yüksek deneme sayısı 150 civarındaydı).

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

Bunu, öğeleri kimliğe, metne, ana öğeye vb. Göre bulan tüm yöntemlerde kullanıyorum:

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

findById(int itemId)waitForElementUntilDisplayed(element);
Örneğinizde

Sadece içeri girip bunun bence en iyi çözüm olduğunu söylemek istedim. IdlingResource5 saniyelik yoklama oranı ayrıntı düzeyi nedeniyle (kullanım durumum için çok büyük) e-postaları benim için yeterli değil. Kabul edilen cevap benim için de işe yaramıyor (neden bu cevabın uzun yorum beslemesine zaten dahil edildiğinin açıklaması). Bunun için teşekkürler! Fikrinizi aldım ve kendi çözümümü yaptım ve büyüleyici bir şekilde çalışıyor.
oaskamay

Evet, mevcut aktivitede olmayan unsurları beklemek istediğimde benim için de işe yarayan tek çözüm budur.
guilhermekrz

3

Espresso, testlerde uyku () çağrılarını önlemek için üretilmiştir. Testiniz, test edilen faaliyetin sorumluluğu olan bir IP girmek için bir iletişim kutusu açmamalıdır.

Öte yandan, UI testiniz şunları yapmalıdır:

  • IP iletişim kutusunun görünmesini bekleyin
  • IP adresini doldurun ve enter'a tıklayın
  • Düğmenizin görünmesini bekleyin ve tıklayın

Test şunun gibi görünmelidir:

// 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, testlerinizi yürütmeden önce hem UI iş parçacığında hem de AsyncTask havuzunda olan her şeyin bitmesini bekler.

Testlerinizin uygulama sorumluluğunuz olan hiçbir şeyi yapmaması gerektiğini unutmayın. "İyi bilgilendirilmiş bir kullanıcı" gibi davranmalıdır: Tıklayan, ekranda bir şeyin gösterildiğini doğrulayan, ancak aslında bileşenlerin kimliklerini bilen bir kullanıcı


2
Örnek kodunuz, aslında sorumda yazdığım kodun aynısı.
Chad Bingham

@Binghammer demek istediğim, testin kullanıcı gibi davranması gerektiğidir. Belki de kaçırdığım nokta IP.enterIP () yönteminizin yaptığı şeydir. Sorunuzu düzenleyip netleştirebilir misiniz?
Bolhoso

Yorumlarım ne işe yaradığını söylüyor. IP diyaloğunu dolduran sadece espressoda bir yöntemdir. Hepsi UI.
Chad Bingham

mm ... tamam, yani haklısın, benim testim temelde aynı şeyi yapıyor. UI iş parçacığı veya AsyncTasks dışında bir şey mi yapıyorsunuz?
Bolhoso

16
Espresso, bu cevabın kodu ve metninin ima ettiği gibi çalışmıyor. ViewInteraction'daki bir kontrol çağrısı, verilen Matcher başarılı olana kadar beklemeyecek, bunun yerine koşul yerine getirilmezse hemen başarısız olacaktır. Bunu yapmanın doğru yolu, bu cevapta belirtildiği gibi AsyncTasks kullanmak veya bir şekilde mümkün değilse, test yürütmeye devam etmek tamam olduğunda Espresso'nun UiController'ı bilgilendirecek bir IdlingResource uygulamaktır.
haffax

2

Espresso Boşta Dönme Kaynağını kullanmalısınız, bu CodeLab'de önerilmektedir

Boştaki bir kaynak, sonuçları bir UI testindeki sonraki işlemleri etkileyen zaman uyumsuz bir işlemi temsil eder. Boşta kalan kaynakları Espresso ile kaydederek, uygulamanızı test ederken bu eşzamansız işlemleri daha güvenilir bir şekilde doğrulayabilirsiniz.

Presenter'dan Eşzamansız çağrı örneği

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

Bağımlılıklar

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

Androidx için

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

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

IdlingResource Örneği: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample


0

Bunun için Idling Resources'ı kullanmanın en iyisi olduğunu düşünsem de ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ) muhtemelen bunu bir geri dönüş olarak kullanabilirsiniz:

/**
 * 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();
        }
    }
}

ve sonra bunu kodunuzda şu şekilde çağırın:

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

onun yerine

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

Bu ayrıca, eylemleri görüntülemek ve onayları görüntülemek için zaman aşımı eklemenize de olanak tanır.


Herhangi bir Test Espresso test senaryosu için aşağıdaki tek satırlık kodu kullanın: SystemClock.sleep (1000); // 1 Second
Nikunjkumar Kapupara

Benim için bu sadece bu satırı değiştirerek çalışır return new TimedViewInteraction(Espresso.onView(viewMatcher));ilereturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger

0

Yardımcı programım, hatasız geçene veya bir zaman aşımından sonra atılabilir olana kadar çalıştırılabilir veya çağrılabilir yürütmeyi tekrarlar. Espresso testleri için mükemmel çalışıyor!

Son görünüm etkileşiminin (düğme tıklaması) bazı arka plan iş parçacıklarını (ağ, veritabanı vb.) Etkinleştirdiğini varsayalım. Sonuç olarak, yeni bir ekran görünmeli ve bir sonraki adımımızda kontrol etmek istiyoruz, ancak yeni ekranın ne zaman test edilmeye hazır olacağını bilmiyoruz.

Önerilen yaklaşım, uygulamanızı testinize iş parçacığı durumları hakkında mesajlar göndermeye zorlamaktır. Bazen OkHttp3IdlingResource gibi yerleşik mekanizmaları kullanabiliriz. Diğer durumlarda, yalnızca desteği test etmek için uygulama kaynaklarınızın farklı yerlerine kod parçaları eklemelisiniz (uygulama mantığını bilmelisiniz!). Dahası, tüm animasyonlarınızı kapatmalıyız (kullanıcı arayüzünün bir parçası olmasına rağmen).

Diğer yaklaşım beklemedir, örneğin SystemClock.sleep (10000). Ancak ne kadar bekleyeceğimizi bilmiyoruz ve uzun gecikmeler bile başarıyı garanti edemez. Öte yandan, testiniz uzun sürecek.

Yaklaşımım, etkileşimi görüntülemek için zaman koşulu eklemektir. Örneğin, 10000 mc (zaman aşımı) sırasında yeni ekranın görünmesi gerektiğini test ediyoruz. Ancak beklediğimiz kadar hızlı bir şekilde kontrol etmiyoruz (örneğin her 100 ms'de bir) Elbette, test iş parçacığını bu şekilde engelleriz, ancak genellikle, bu gibi durumlarda tam da ihtiyacımız olan şey budur.

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())));

Bu benim sınıf kaynağım:

/**
 * 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

Bu, Android Testleri için Kotlin'de kullandığım bir yardımcı. Benim durumumda, sunucu yanıtını taklit etmek için longOperation kullanıyorum, ancak bunu amacınıza göre değiştirebilirsiniz.

@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

Bunu yapma yöntemimi karışıma ekleyeceğim:

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

Şöyle aradı:

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

SuspendUntilSuccess işlevine maksimum yineleme, yineleme uzunluğu vb. Gibi parametreler ekleyebilirsiniz.

Hala boşta olan kaynakları kullanmayı tercih ediyorum, ancak örneğin cihazdaki yavaş animasyonlar nedeniyle testler işe yaradığında, bu işlevi kullanıyorum ve iyi çalışıyor. Elbette başarısız olmadan önce olduğu gibi 5 saniyeye kadar askıda kalabilir, bu nedenle başarılı olacak eylem asla başarılı olmazsa testlerinizin yürütme süresini uzatabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.