เอสเปรสโซ: Thread.sleep ();


102

เอสเปรสโซอ้างว่าไม่จำเป็นThread.sleep();แต่รหัสของฉันใช้ไม่ได้เว้นแต่ฉันจะรวมไว้ ฉันกำลังเชื่อมต่อกับ IP ขณะเชื่อมต่อกล่องโต้ตอบความคืบหน้าจะปรากฏขึ้น ฉันต้องการsleepรอให้กล่องโต้ตอบปิด นี่คือตัวอย่างการทดสอบของฉันที่ฉันใช้:

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

ฉันได้พยายามรหัสนี้ด้วยและโดยไม่ต้องThread.sleep();แต่ก็กล่าวว่าR.id.Buttonไม่ได้อยู่ วิธีเดียวที่ฉันจะทำให้มันทำงานได้คือการนอนหลับ

นอกจากนี้ฉันได้ลองแทนที่Thread.sleep();ด้วยสิ่งที่ชอบgetInstrumentation().waitForIdleSync();และยังไม่มีโชค

นี่เป็นวิธีเดียวที่จะทำได้หรือไม่? หรือฉันขาดอะไรไป?

ขอบคุณล่วงหน้า.


เป็นไปได้ไหมที่คุณจะใส่ในขณะที่ลูปที่ไม่ต้องการอย่างไรก็ตามคุณต้องการบล็อกการโทร
kedark

โอเค .. ให้ฉันอธิบาย ข้อเสนอแนะ 2 ประการสำหรับคุณประการที่ 1) ใช้กลไกแบบโทรกลับ ในการเชื่อมต่อ - สร้างการโทรหนึ่งวิธีและแสดงมุมมอง 2) คุณต้องการสร้างความล่าช้าระหว่าง IP.enterIP (); และ onView (.... ) เพื่อให้คุณสามารถใส่ while loop ซึ่งจะสร้างความล่าช้าในการเรียก onview (.. ) ... แต่ฉันรู้สึกว่าถ้าเป็นไปได้โปรดเลือกตัวเลือกหมายเลข 1 (การสร้างการโทรกลับ กลไก) ...
kedark

@kedark ใช่นั่นคือตัวเลือก แต่นั่นคือทางออกของเอสเปรสโซ่หรือไม่?
Chad Bingham

มีความคิดเห็นที่ยังไม่ได้ตอบในคำถามของคุณคุณช่วยตอบได้ไหม
Bolhoso

@Bolhoso คำถามอะไร?
Chad Bingham

คำตอบ:


111

แนวทางที่ถูกต้องในใจของฉันคือ:

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

จากนั้นรูปแบบการใช้งานจะเป็น:

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

3
ขอบคุณอเล็กซ์ทำไมคุณถึงเลือกตัวเลือกนี้กับ IdlingResource หรือ AsyncTasks
Tim Boland

1
นี่เป็นวิธีการแก้ปัญหาโดยส่วนใหญ่แล้วเอสเปรสโซจะทำงานได้โดยไม่มีปัญหาใด ๆ และ 'รหัสรอ' พิเศษ จริงๆแล้วฉันลองหลายวิธีและคิดว่านี่เป็นสถาปัตยกรรม / การออกแบบเอสเปรสโซที่เข้ากันมากที่สุด
Oleksandr Kucherenko

1
@ AlexK นี้ทำให้เพื่อนร่วมวันของฉัน!
dawid gdanski

1
สำหรับฉันมันล้มเหลวสำหรับ api <= 19 ที่บรรทัดใหม่ PerformException.Builder ()
Prabin Timsina

4
ฉันหวังว่าคุณจะเข้าใจว่าเป็นตัวอย่างคุณสามารถคัดลอก / วางและแก้ไขตามความต้องการของตัวเอง เป็นความรับผิดชอบของคุณโดยสมบูรณ์ที่จะใช้มันอย่างถูกต้องตามความต้องการทางธุรกิจของตัวเองไม่ใช่ของฉัน
Oleksandr Kucherenko

47

ขอบคุณ AlexK สำหรับคำตอบที่ยอดเยี่ยมของเขา มีหลายกรณีที่คุณต้องทำให้โค้ดล่าช้า ไม่จำเป็นต้องรอการตอบสนองของเซิร์ฟเวอร์ แต่อาจต้องรอให้แอนิเมชั่นเสร็จสิ้น โดยส่วนตัวฉันมีปัญหากับ Espresso idolingResources (ฉันคิดว่าเรากำลังเขียนโค้ดหลายบรรทัดสำหรับเรื่องง่ายๆ) ดังนั้นฉันจึงเปลี่ยนวิธีที่ AlexK ทำเป็นโค้ดต่อไปนี้:

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

ดังนั้นคุณสามารถสร้างDelayคลาสและใส่วิธีการนี้เพื่อให้เข้าถึงได้ง่าย คุณสามารถใช้ในชั้นเรียนการทดสอบของคุณในลักษณะเดียวกัน:onView(isRoot()).perform(waitFor(5000));


7
วิธีการดำเนินการสามารถทำให้ง่ายขึ้นด้วยหนึ่งบรรทัดเช่นนี้: uiController.loopMainThreadForAtLeast (มิลลิวินาที);
Yair Kukielka

น่ากลัวฉันไม่รู้ว่า: thumbs_up @YairKukielka
Hesam

ใช่สำหรับการรอที่วุ่นวาย
TWiStErRob

น่ากลัว ฉันค้นหาสิ่งนั้นมานานแล้ว +1 สำหรับวิธีง่ายๆสำหรับปัญหาการรอคอย
Tobias Reich

วิธีที่ดีกว่ามากในการเพิ่มความล่าช้าแทนการใช้Thread.sleep()
Wahib Ul Haq

23

ฉันสะดุดกับหัวข้อนี้เมื่อต้องการคำตอบสำหรับปัญหาที่คล้ายกันซึ่งฉันกำลังรอการตอบสนองของเซิร์ฟเวอร์และการเปลี่ยนแปลงการเปิดเผยองค์ประกอบตามการตอบสนอง

ในขณะที่วิธีแก้ปัญหาข้างต้นช่วยได้อย่างแน่นอนในที่สุดฉันก็พบตัวอย่างที่ยอดเยี่ยมนี้จาก Chiukiและตอนนี้ใช้วิธีการนั้นเป็นไปเมื่อใดก็ตามที่ฉันรอให้การดำเนินการเกิดขึ้นในช่วงที่แอปไม่ได้ใช้งาน

ฉันได้เพิ่มElapsedTimeIdlingResource ()ลงในคลาสยูทิลิตี้ของฉันเองแล้วตอนนี้สามารถใช้มันเป็นทางเลือกที่เหมาะสมกับเอสเปรสโซได้อย่างมีประสิทธิภาพและตอนนี้การใช้งานนั้นดีและสะอาด:

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

ฉันได้รับ I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceข้อผิดพลาด ความคิดใด ๆ ฉันใช้ Proguard แต่ปิดการใช้งานที่ทำให้สับสน
Anthony

ลองเพิ่ม-keepคำสั่งสำหรับคลาสที่ไม่พบเพื่อให้แน่ใจว่า ProGuard ไม่ได้ลบออกโดยไม่จำเป็น ข้อมูลเพิ่มเติมที่นี่: developer.android.com/tools/help/proguard.html#keep-code
MattMatt

ฉันโพสต์คำถามstackoverflow.com/questions/36859528/… . ชั้นเรียนอยู่ใน seed.txt และ mapping.txt
Anthony

2
หากคุณจำเป็นต้องเปลี่ยนนโยบายการไม่ทำงานคุณอาจใช้ทรัพยากรที่ไม่ได้ใช้งานอย่างถูกต้อง ในระยะยาวการลงทุนเวลาเพื่อแก้ไขปัญหานั้นจะดีกว่ามาก วิธีนี้จะนำไปสู่การทดสอบที่ช้าและไม่สม่ำเสมอในที่สุด ตรวจสอบgoogle.github.io/android-testing-support-library/docs/espresso/...
โฮเซ่Alcérreca

คุณค่อนข้างถูกต้อง คำตอบนี้มีอายุมากกว่าหนึ่งปีและตั้งแต่นั้นมาพฤติกรรมของทรัพยากรที่ไม่ได้ใช้งานได้ดีขึ้นเช่นกรณีการใช้งานแบบเดียวกับที่ฉันใช้โค้ดด้านบนในตอนนี้ใช้งานได้นอกกรอบตรวจจับไคลเอ็นต์ API ที่ถูกล้อเลียนได้อย่างเหมาะสม - เราไม่ใช้สิ่งที่กล่าวมาข้างต้นอีกต่อไป ElapsedTimeIdlingResource ในการทดสอบเครื่องมือของเราด้วยเหตุผลนั้น (แน่นอนคุณสามารถ Rx ได้ทุกสิ่งซึ่งไม่จำเป็นต้องแฮ็คในช่วงเวลารอคอย) ที่กล่าวว่าวิธีการของ Google ในการทำสิ่งที่ไม่เสมอที่ดีที่สุด: philosophicalhacker.com/post/...
MattMatt

18

ฉันคิดว่ามันง่ายกว่าที่จะเพิ่มบรรทัดนี้:

SystemClock.sleep(1500);

รอจำนวนมิลลิวินาทีที่กำหนด (ของ uptimeMillis) ก่อนที่จะกลับมา คล้ายกับการนอนหลับ (long) แต่ไม่โยน InterruptedException; เหตุการณ์ขัดจังหวะ () ถูกเลื่อนออกไปจนกว่าการดำเนินการขัดจังหวะครั้งถัดไป ไม่ย้อนกลับจนกว่าจะพ้นจำนวนมิลลิวินาทีที่ระบุเป็นอย่างน้อย


Expresso คือการหลีกเลี่ยงการนอนหลับที่เข้ารหัสซึ่งทำให้เกิดการทดสอบที่ไม่สม่ำเสมอ หากเป็นกรณีนี้ฉันก็สามารถใช้เครื่องมือ blackbox เช่น appium
Emjey

6

คุณสามารถใช้วิธีการบาริสต้า:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista เป็นห้องสมุดที่ห่อ Espresso เพื่อหลีกเลี่ยงการเพิ่มรหัสทั้งหมดที่จำเป็นสำหรับคำตอบที่ยอมรับ และนี่คือลิงค์! https://github.com/SchibstedSpain/Barista


ฉันไม่เข้าใจความแตกต่างระหว่างสิ่งนี้กับการนอนหลับ
Pablo Caviglia

สุจริตผมจำไม่ได้ซึ่งในวิดีโอจาก Google Thread.sleep()ผู้ชายบอกว่าเราควรใช้วิธีนี้ในการทำการนอนหลับแทนการร่วมกัน ขออภัย! มันเป็นวิดีโอแรกที่ Google ทำเกี่ยวกับ Espresso แต่ฉันจำไม่ได้ว่าอันไหน ... เมื่อหลายปีก่อน ขออภัย! : ·) โอ้! แก้ไข! ฉันใส่ลิงค์ไปยังวิดีโอในประชาสัมพันธ์ที่ฉันเปิดเมื่อสามปีก่อน ลองดูสิ! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

คล้ายกับคำตอบนี้แต่ใช้การหมดเวลาแทนการพยายามและสามารถเชื่อมโยงกับ ViewInteractions อื่น ๆ ได้:

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

การใช้งาน:

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

4

ฉันใหม่กับการเขียนโค้ดและเอสเปรสโซดังนั้นในขณะที่ฉันรู้ว่าวิธีแก้ปัญหาที่ดีและสมเหตุสมผลคือการใช้การไม่ทำงาน แต่ฉันก็ยังไม่ฉลาดพอที่จะทำเช่นนั้น

จนกว่าฉันจะมีความรู้มากขึ้นฉันยังคงต้องการการทดสอบเพื่อดำเนินการต่อดังนั้นตอนนี้ฉันกำลังใช้วิธีแก้ปัญหาสกปรกนี้ซึ่งพยายามหาองค์ประกอบหลายครั้งหยุดถ้าพบและถ้าไม่เป็นเวลาสั้น ๆ จะนอนหลับและเริ่มทำงาน อีกครั้งจนกว่าจะถึงจำนวนสูงสุดของความพยายาม (จำนวนครั้งสูงสุดที่ผ่านมาคือประมาณ 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;
}

ฉันใช้สิ่งนี้ในทุกวิธีที่ค้นหาองค์ประกอบด้วย ID, ข้อความ, พาเรนต์ ฯลฯ :

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

ในตัวอย่างของคุณfindById(int itemId)เมธอดจะส่งคืนองค์ประกอบ (ซึ่งอาจเป็นโมฆะ) ไม่ว่าwaitForElementUntilDisplayed(element);ผลตอบแทนจะเป็นจริงหรือเท็จ .... ดังนั้นไม่เป็นไร
mbob

แค่อยากจะตีระฆังและบอกว่านี่เป็นทางออกที่ดีที่สุดในความคิดของฉัน IdlingResources ไม่เพียงพอสำหรับฉันเนื่องจากความละเอียดของอัตราการสำรวจความคิดเห็น 5 วินาที (ใหญ่เกินไปสำหรับกรณีการใช้งานของฉัน) คำตอบที่ยอมรับก็ใช้ไม่ได้กับฉันเช่นกัน (คำอธิบายว่าเหตุใดจึงรวมอยู่ในฟีดความคิดเห็นแบบยาวของคำตอบนั้นแล้ว) ขอบคุณสำหรับสิ่งนี้! ฉันใช้ความคิดของคุณและสร้างวิธีแก้ปัญหาของฉันเองและมันก็เป็นเสน่ห์
oaskamay

ใช่นี่เป็นทางออกเดียวที่ใช้ได้ผลสำหรับฉันเช่นกันเมื่อต้องการรอองค์ประกอบที่ไม่ได้อยู่ในกิจกรรมปัจจุบัน
guilhermekrz

3

เอสเปรสโซถูกสร้างขึ้นเพื่อหลีกเลี่ยงการนอนหลับ () การโทรในการทดสอบ การทดสอบของคุณไม่ควรเปิดกล่องโต้ตอบเพื่อป้อน IP นั่นควรเป็นความรับผิดชอบของกิจกรรมที่ทดสอบ

ในทางกลับกันการทดสอบ UI ของคุณควร:

  • รอให้กล่องโต้ตอบ IP ปรากฏขึ้น
  • กรอกที่อยู่ IP และคลิก Enter
  • รอให้ปุ่มของคุณปรากฏขึ้นแล้วคลิก

การทดสอบควรมีลักษณะดังนี้:

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

เอสเปรสโซรอให้ทุกสิ่งที่เกิดขึ้นทั้งในเธรด UI และพูล AsyncTask เสร็จสิ้นก่อนดำเนินการทดสอบของคุณ

โปรดจำไว้ว่าการทดสอบของคุณไม่ควรทำอะไรที่เป็นความรับผิดชอบในการสมัครของคุณ ควรทำตัวเหมือน "ผู้ใช้ที่มีข้อมูลดี": ผู้ใช้ที่คลิกตรวจสอบว่ามีบางสิ่งปรากฏบนหน้าจอ แต่ตามความเป็นจริงควรทราบรหัสของส่วนประกอบ


2
รหัสตัวอย่างของคุณเป็นรหัสเดียวกับที่ฉันเขียนในคำถามของฉัน
Chad Bingham

@Binghammer สิ่งที่ฉันหมายถึงคือการทดสอบควรทำตัวเหมือนผู้ใช้ประพฤติ บางทีประเด็นที่ฉันขาดหายไปคือวิธีการ IP.enterIP () ของคุณทำ คุณสามารถแก้ไขคำถามและชี้แจงได้หรือไม่?
Bolhoso

ความคิดเห็นของฉันบอกว่ามันทำอะไร เป็นเพียงวิธีการในเอสเพรสโซที่เติมกล่องโต้ตอบ IP เป็น UI ทั้งหมด
Chad Bingham

อืม ... โอเคคุณพูดถูกการทดสอบของฉันก็ทำเหมือนกัน คุณทำบางอย่างจากเธรด UI หรือ AsyncTasks หรือไม่?
Bolhoso

16
เอสเปรสโซไม่ทำงานเหมือนรหัสและข้อความของคำตอบนี้ดูเหมือนจะบอกเป็นนัยว่า การเรียกตรวจสอบบน ViewInteraction จะไม่รอจนกว่า Matcher ที่กำหนดจะสำเร็จ แต่จะล้มเหลวทันทีหากไม่ตรงตามเงื่อนไข วิธีที่ถูกต้องคือการใช้ AsyncTasks ตามที่กล่าวไว้ในคำตอบนี้หรือหากไม่สามารถทำได้ให้ใช้ IdlingResource ที่จะแจ้งให้ UiController ของ Espresso ทราบเมื่อสามารถดำเนินการทดสอบต่อได้
haffax

2

คุณควรใช้ Espresso Idling Resource แนะนำที่CodeLabนี้

ทรัพยากรที่ไม่ทำงานแสดงถึงการดำเนินการแบบอะซิงโครนัสซึ่งผลลัพธ์จะส่งผลต่อการดำเนินการในภายหลังในการทดสอบ UI ด้วยการลงทะเบียนทรัพยากรที่ไม่ได้ใช้งานกับ Espresso คุณสามารถตรวจสอบการทำงานแบบอะซิงโครนัสเหล่านี้ได้อย่างน่าเชื่อถือยิ่งขึ้นเมื่อทดสอบแอปของคุณ

ตัวอย่างการโทรแบบอะซิงโครนัสจากผู้นำเสนอ

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

การพึ่งพา

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

สำหรับ 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 อย่างเป็นทางการ: https://github.com/googlecodelabs/android-testing

IdlingResource ตัวอย่าง: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample


0

แม้ว่าฉันคิดว่าควรใช้ Idling Resources สำหรับสิ่งนี้ ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ) คุณอาจใช้สิ่งนี้เป็นทางเลือก:

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

จากนั้นเรียกมันในรหัสของคุณเช่น:

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

แทน

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

นอกจากนี้ยังช่วยให้คุณเพิ่มระยะหมดเวลาสำหรับการดูการดำเนินการและดูการยืนยัน


ใช้โค้ดบรรทัดเดียวด้านล่างนี้เพื่อทดสอบกรณีทดสอบ Test Espresso: SystemClock.sleep (1000); // 1 วินาที
Nikunjkumar Kapupara

สำหรับฉันสิ่งนี้ใช้ได้ผลโดยการเปลี่ยนบรรทัดนี้return new TimedViewInteraction(Espresso.onView(viewMatcher));ด้วยreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger

0

โปรแกรมอรรถประโยชน์ของฉันทำซ้ำการเรียกใช้งานหรือเรียกได้จนกว่าจะผ่านไปโดยไม่มีข้อผิดพลาดหรือโยนทิ้งได้หลังจากหมดเวลา เหมาะสำหรับการทดสอบ Espresso!

สมมติว่าการโต้ตอบมุมมองสุดท้าย (การคลิกปุ่ม) เปิดใช้งานเธรดพื้นหลังบางส่วน (เครือข่ายฐานข้อมูล ฯลฯ ) ด้วยเหตุนี้หน้าจอใหม่ควรปรากฏขึ้นและเราต้องการตรวจสอบในขั้นตอนต่อไป แต่เราไม่รู้ว่าหน้าจอใหม่จะพร้อมสำหรับการทดสอบเมื่อใด

แนวทางที่แนะนำคือบังคับให้แอปของคุณส่งข้อความเกี่ยวกับสถานะเธรดไปยังการทดสอบของคุณ บางครั้งเราสามารถใช้กลไกในตัวเช่น OkHttp3IdlingResource ในกรณีอื่น ๆ คุณควรแทรกชิ้นส่วนโค้ดในตำแหน่งต่างๆของแหล่งที่มาของแอปของคุณ (คุณควรรู้ตรรกะของแอป!) เพื่อรองรับการทดสอบเท่านั้น ยิ่งไปกว่านั้นเราควรปิดภาพเคลื่อนไหวทั้งหมดของคุณ (แม้ว่าจะเป็นส่วนหนึ่งของ UI ก็ตาม)

อีกวิธีหนึ่งกำลังรออยู่เช่น SystemClock.sleep (10000) แต่เราไม่รู้ว่าต้องรอนานแค่ไหนและแม้แต่ความล่าช้าที่ยาวนานก็ไม่สามารถรับประกันความสำเร็จได้ ในทางกลับกันการทดสอบของคุณจะใช้เวลานาน

แนวทางของฉันคือการเพิ่มเงื่อนไขเวลาเพื่อดูการโต้ตอบ เช่นเราทดสอบว่าหน้าจอใหม่ควรปรากฏในช่วง 10,000 mc (หมดเวลา) แต่เราไม่รอและตรวจสอบให้เร็วที่สุดเท่าที่เราต้องการ (เช่นทุกๆ 100 ms) แน่นอนว่าเราบล็อกเธรดการทดสอบด้วยวิธีนี้ แต่โดยปกติแล้วมันเป็นเพียงสิ่งที่เราต้องการในกรณีเช่นนี้

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

นี่คือแหล่งที่มาของชั้นเรียนของฉัน:

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


0

นี่คือตัวช่วยที่ฉันใช้ใน Kotlin สำหรับการทดสอบ Android ในกรณีของฉันฉันใช้ longOperation เพื่อเลียนแบบการตอบสนองของเซิร์ฟเวอร์ แต่คุณสามารถปรับแต่งตามวัตถุประสงค์ของคุณได้

@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

ฉันจะเพิ่มวิธีการทำสิ่งนี้ลงในส่วนผสม:

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

เรียกแบบนี้ว่า

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

คุณสามารถเพิ่มพารามิเตอร์เช่นการวนซ้ำสูงสุดความยาวการวนซ้ำ ฯลฯ ให้กับฟังก์ชัน SuspendUntilSuccess

ฉันยังคงชอบใช้ทรัพยากรที่ไม่ได้ใช้งาน แต่เมื่อการทดสอบทำงานเนื่องจากภาพเคลื่อนไหวช้าบนอุปกรณ์ฉันใช้ฟังก์ชันนี้และทำงานได้ดี แน่นอนว่าสามารถวางสายได้นานถึง 5 วินาทีก่อนที่จะล้มเหลวดังนั้นจึงสามารถเพิ่มเวลาในการดำเนินการทดสอบของคุณได้หากการดำเนินการเพื่อให้ประสบความสำเร็จไม่เคยประสบความสำเร็จ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.