에스프레소 : Thread.sleep ();


102

Espresso는에 대한 필요가 없다고 주장 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();여전히 행운.

이것이 유일한 방법입니까? 아니면 내가 뭔가를 놓치고 있습니까?

미리 감사드립니다.


어쨌든 원하지 않는 While 루프를 넣을 수 있습니까?
kedark

좋아요 .. 설명하겠습니다. 2 가지 제안 1st) 콜백과 같은 메커니즘을 구현합니다. on-connection-establish는 하나의 메소드를 호출하고 뷰를 보여줍니다. 2nd) IP.enterIP (); 사이에 지연을 만들고 싶습니다. 그리고 onView (....) 그래서 당신은 onview (..)를 호출하는 유사한 종류의 지연을 생성 할 while 루프를 넣을 수 있습니다 ...하지만 가능하다면 옵션 No 1을 선호하십시오. (콜백 생성 메커니즘) ...
kedark

@kedark 예, 옵션이지만 Espresso의 솔루션입니까?
Chad Bingham

질문에 답변되지 않은 댓글이 있습니다. 답변 해 주시겠습니까?
Bolhoso 2014

@ Bolhoso, 무슨 질문?
Chad Bingham 2014

답변:


110

내 마음에 올바른 접근 방식은 다음과 같습니다.

/** 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
감사합니다 Alex, IdlingResource 또는 AsyncTasks 대신이 옵션을 선택한 이유는 무엇입니까?
Tim Boland 2014 년

1
대부분의 경우 Espresso가 문제와 특별한 '대기 코드'없이 작업을 수행하는 해결 방법입니다. 저는 실제로 여러 가지 방법을 시도하고 이것이 가장 일치하는 Espresso 아키텍처 / 디자인이라고 생각합니다.
Oleksandr Kucherenko 2014 년

1
@AlexK 이것은 내 하루 친구를 만들었습니다!
dawid gdanski

1
나를 위해, 그것은 api <= 19에 대해 실패하고, 줄에서 new PerformException.Builder ()를
던졌습니다

4
샘플이므로 필요에 따라 복사 / 붙여 넣기 및 수정할 수 있음을 이해하시기 바랍니다. 내 것이 아닌 자신의 비즈니스 요구에 적절하게 사용하는 것은 전적으로 귀하의 책임입니다.
Oleksandr Kucherenko 2016

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
perform 메소드는 다음과 같이 한 줄로 단순화 할 수도 있습니다. uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka

굉장하다, 나는 그것을 몰랐다 : thumbs_up @YairKukielka
Hesam

바쁜 기다림을 아끼지 않습니다.
TWiStErRob

대박. 나는 그것을 오랫동안 찾고 있었다. 대기 문제에 대한 간단한 해결책은 +1.
Tobias Reich

훨씬 더 좋은 방법은 사용하는 대신 지연을 추가Thread.sleep()
Wahib UL 하크

23

서버 응답을 기다리고 응답을 기반으로 요소의 가시성을 변경하는 유사한 문제에 대한 답변을 찾을 때이 스레드를 우연히 발견했습니다.

위의 솔루션이 확실히 도움이되었지만 결국 chiuki에서이 훌륭한 예제를 찾았고 이제 앱 유휴 기간 동안 작업이 발생하기를 기다릴 때마다 해당 접근 방식을 사용합니다.

내 자신의 유틸리티 클래스 에 ElapsedTimeIdlingResource () 를 추가 했으며 이제이를 Espresso-proper 대안으로 효과적으로 사용할 수 있으며 이제 사용이 멋지고 깨끗합니다.

// 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를 사용하지만 난독 화를 비활성화합니다.
안토니

-keep찾을 수없는 클래스에 대한 문을 추가하여 ProGuard가 불필요한 클래스를 제거하지 않도록합니다. 여기에 더 많은 정보 : developer.android.com/tools/help/proguard.html#keep-code
MattMatt

질문 stackoverflow.com/questions/36859528/… 게시합니다 . 클래스는 seed.txt 및 mapping.txt에
앤서니

2
유휴 정책을 변경해야하는 경우 유휴 리소스를 올바르게 구현하지 않을 수 있습니다. 장기적으로 그것을 고치는 데 시간을 투자하는 것이 훨씬 낫습니다. 이 방법은 결국 느리고 불안정한 테스트로 이어질 것입니다. 확인 google.github.io/android-testing-support-library/docs/espresso/...
호세 Alcérreca

당신 말이 맞습니다. 이 답변은 1 년이 넘었으며 그 이후로 유휴 리소스의 동작이 개선되어 위 코드를 사용한 동일한 사용 사례가 즉시 작동하여 모의 API 클라이언트를 올바르게 감지합니다. 이러한 이유로 계측 테스트의 ElapsedTimeIdlingResource. (물론 모든 것을 Rx 할 수 있으므로 대기 시간에 해킹 할 필요가 없습니다.) 즉, Google의 작업 방식이 항상 최고인 것은 아닙니다. philosophicalhacker.com/post/… .
MattMatt

18

이 줄을 추가하는 것이 더 쉽습니다.

SystemClock.sleep(1500);

반환하기 전에 주어진 밀리 초 (uptimeMillis)를 기다립니다. sleep (long)과 유사하지만 InterruptedException을 발생시키지 않습니다. interrupt () 이벤트는 다음 인터럽트 가능한 작업까지 연기됩니다. 최소한 지정된 시간 (밀리 초)이 경과 할 때까지 반환되지 않습니다.


Expresso는 불안정한 테스트를 유발하는 이러한 하드 코딩 된 수면을 피하는 것입니다. 이것은 나뿐만 아니라 appium 같은 블랙 박스 툴 갈 수있는 경우에 해당
Emjey

6

Barista 방법을 사용할 수 있습니다.

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista는 수락 된 답변에 필요한 모든 코드를 추가하지 않도록 Espresso를 래핑하는 라이브러리입니다. 그리고 여기에 링크가 있습니다! https://github.com/SchibstedSpain/Barista


나는 이것과 스레드 잠을하는 것 사이의 차이를 이해하지 못한다
Pablo Caviglia

솔직히, 어떤 사람이 구글의 어떤 비디오에서 우리가 공통점을 만드는 대신이 방법으로 잠을 자야한다고 말했는지 기억이 나지 않습니다 Thread.sleep(). 죄송합니다! Google이 Espresso에 대해 만든 첫 번째 동영상 중 일부에 있었지만 어떤 동영상인지 기억이 나지 않습니다. 몇 년 전이었습니다. 죄송합니다! : ·) 오! 편집하다! 3 년 전에 공개 한 PR에 영상 링크를 넣었습니다. 확인 해봐! 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

저는 코딩과 Espresso를 처음 사용하기 때문에 유휴 상태를 사용하는 것이 좋고 합리적인 해결책이라는 것을 알고 있지만 아직 그렇게 할만큼 지능적이지는 않습니다.

지식이 더 많아 질 때까지 어떻게 든 실행하려면 테스트가 필요하므로 지금은 요소를 찾으려고 여러 번 시도하고 발견하면 중지하고 그렇지 않으면 잠시 잠자고 시작하는이 더러운 솔루션을 사용하고 있습니다. 최대 시도 횟수에 도달 할 때까지 다시 시도합니다 (지금까지 가장 높은 시도 횟수는 약 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)방법 (NULL이 될 수 있음) 여부 요소를 반환합니다 waitForElementUntilDisplayed(element);확인 아니다 그, 그래서 .... 반환 참 또는 거짓
mbob

내 의견으로는 이것이 최선의 해결책이라고 말하고 싶었습니다. IdlingResources는 5 초 폴링 속도 세분성 (내 사용 사례에 비해 너무 큼)으로 인해 충분하지 않습니다. 수락 된 답변은 나에게도 효과가 없습니다 (왜 해당 답변의 긴 댓글 피드에 이미 포함되어 있음). 감사합니다! 나는 당신의 아이디어를 가지고 나만의 해결책을 만들었고 그것은 매력처럼 작동합니다.
oaskamay

예, 이것은 현재 활동에없는 요소를 기다리고 싶을 때 저에게도 효과가 있었던 유일한 솔루션입니다.
guilhermekrz

3

Espresso는 테스트에서 sleep () 호출을 피하기 위해 만들어졌습니다. 테스트는 테스트 된 활동의 책임 인 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());

Espresso는 테스트를 실행하기 전에 UI 스레드와 AsyncTask 풀에서 발생하는 모든 작업이 완료 될 때까지 기다립니다.

테스트는 애플리케이션의 책임을 수행해서는 안된다는 것을 기억하십시오. 이것은 "잘 알고있는 사용자"처럼 행동해야합니다. 클릭하고 화면에 무언가가 표시되는지 확인하지만 실제로는 구성 요소의 ID를 알고있는 사용자


2
귀하의 예제 코드는 본질적으로 내 질문에서 작성한 것과 동일한 코드입니다.
Chad Bingham

@Binghammer 내 말은 테스트가 사용자가 행동하는 것처럼 행동해야한다는 것입니다. 내가 놓친 요점은 IP.enterIP () 메서드가하는 일입니다. 질문을 편집하고 명확히 할 수 있습니까?
Bolhoso 2014 년

내 의견은 그것이하는 일을 말합니다. IP 대화 상자를 채우는 것은 에스프레소의 방법 일뿐입니다. 모든 UI입니다.
Chad Bingham

음 ... 그래, 네 말이 맞아, 내 테스트는 기본적으로 똑같아. UI 스레드 또는 AsyncTasks에서 뭔가를합니까?
Bolhoso 2014 년

16
Espresso는이 답변의 코드와 텍스트가 암시하는 것처럼 작동하지 않습니다. ViewInteraction에 대한 확인 호출은 주어진 Matcher가 성공할 때까지 기다리지 않고 조건이 충족되지 않으면 즉시 실패합니다. 이 작업을 수행하는 올바른 방법은이 답변에서 언급했듯이 AsyncTasks를 사용하거나 가능하지 않은 경우 테스트 실행을 진행해도 괜찮을 때 Espresso의 UiController에 알리는 IdlingResource를 구현하는 것입니다.
haffax 2014

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'

공식 저장소 : 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())));
마누엘 Schmitzberger

0

내 유틸리티는 오류없이 통과하거나 시간 초과 후 throw 가능할 때까지 실행 가능 또는 호출 가능 실행을 반복합니다. 에스프레소 테스트에 완벽하게 작동합니다!

마지막 뷰 상호 작용 (버튼 클릭)이 일부 백그라운드 스레드 (네트워크, 데이터베이스 등)를 활성화한다고 가정합니다. 결과적으로 새 화면이 나타나야하며 다음 단계에서 확인하고 싶지만 새 화면이 언제 테스트 될 준비가되었는지 알 수 없습니다.

권장되는 접근 방식은 앱이 스레드 상태에 대한 메시지를 테스트에 보내도록하는 것입니다. 때때로 우리는 OkHttp3IdlingResource와 같은 내장 메커니즘을 사용할 수 있습니다. 다른 경우에는 테스트 지원만을 위해 앱 소스의 다른 위치에 코드 조각을 삽입해야합니다 (앱 로직을 알고 있어야합니다!). 또한 모든 애니메이션을 꺼야합니다 (UI의 일부 임에도 불구하고).

다른 접근 방식은 대기 중입니다 (예 : SystemClock.sleep (10000)). 그러나 우리는 얼마나 오래 기다려야할지 모르고 긴 지연조차도 성공을 보장 할 수 없습니다. 반면에 테스트는 오래 지속됩니다.

내 접근 방식은 상호 작용을보기 위해 시간 조건을 추가하는 것입니다. 예를 들어 10000mc (시간 초과) 동안 새 화면이 표시되는지 테스트합니다. 그러나 우리는 원하는만큼 빨리 확인하지 않고 (예 : 100ms마다) 물론 테스트 스레드를 이러한 방식으로 차단하지만 일반적으로 그런 경우에 필요한 것입니다.

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/alexshr/ca90212e49e74eb201fbc976255b47e0


0

이것은 Android 테스트 용 Kotlin에서 사용중인 도우미입니다. 제 경우에는 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.