Android UIテストガイドのEspresso


Espressoについて
  • Espressoは簡単で使いやすいAndroid UIテストフレームワーク
  • です.
  • Espressoは主に以下の3つの基礎部分から構成されている:
    ViewMatchers -    View        View .
    ViewActions -   Views     ,      .
    ViewAssertions -   Views     ,      .
    
  • Espressoの使用例
    onView(ViewMatcher) //1.  View
          .perform(ViewAction) //2.  View  
               .check(ViewAssertion); //3.  View

    の準備を
    第一歩build.gradleは次の依存性を追加します.
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:latest.version'
    androidTestImplementation 'com.android.support.test:runner:latest.version'
    androidTestImplementation 'com.android.support.test:rules:latest.version'

    第二歩android.defaultConfig次の構成を追加
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    基本的な使い方
    ビューを取得
  • withId方式
  • onView(withId(R.id.my_view))
  • withText方式
  • onView(withText("Hello World!"))

    View動作の実行
  • クリック
  • onView(...).perform(click());
  • テキスト内容入力
  • onView(...).perform(typeText("Hello"), click());
  • scrollToスライド
  • onView(...).perform(scrollTo(), click());

    検査ビュー
  • ビューのテキスト内容をチェック
  • onView(...).check(matches(withText("Hello!")));
  • 検査Viewの表示状態
  • onView(...).check(matches(isDisplayed()));

    シーンの例:
    簡単なログインシーンテスト
    @RunWith(AndroidJUnit4.class)
    public class LoginUITest {
    
        @Rule
        public ActivityTestRule rule=new ActivityTestRule(LogingActivity.class,true);
    
        @Test
        public void login(){
            //login
            onView(withId(R.id.userName)).perform(typeText("Jack"),closeSoftKeyboard());
            onView(withId(R.id.password)).perform(typeText("1234"),closeSoftKeyboard());
            onView(withText("  ")).perform(click());
    
            //verify
            onView(withId(R.id.content)).check(matches(isDisplayed()));
        }
    
    }

    ステップアップ法
    1.IdlingResourceの使用
    通常、私たちの実際のアプリケーションには、ネットワークリクエスト、ピクチャロードなどの非同期タスクがたくさんありますが、Espressoはあなたの非同期タスクがいつ終わるか分からないので、IdlingResourceを借りる必要があります.
    ここで注意しなければならないのは、AsyncTaskまたはAsyncTaskCompat方式の非同期タスクであれば、Espressoはすでに処理されており、追加の処理は必要ありません.
    ステップ1:依存ライブラリの追加
    compile 'com.android.support.test.espresso:espresso-idling-resource:latest.version'

    ステップ2:IdlingResourceインタフェースを定義します.
    public interface IdlingResource {
    
      /**
       *      IdlingResource   
       */
      public String getName();
    
      /**
       *    IdlingResource      .
       */
      public boolean isIdleNow();
    
      /**
                  ResourceCallback  
       */
      public void registerIdleTransitionCallback(ResourceCallback callback);
    
      /**
       *   Espresso  IdlingResource            
       */
      public interface ResourceCallback {
        /**
         *           ,       Espresso
         */
        public void onTransitionToIdle();
      }
    }

    次に例を示します.
    シーン:現在、ユーザーのアイコンが正常に表示されているかどうかをテストする必要があるとします.
    Activityコード
    public class AvatarActivity extends AppCompatActivity{
    
        private ImageView mAvatar;
    
        public static SimpleIdlingResource sIdlingResource=new SimpleIdlingResource();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_idling_resource);
    
            mAvatar= (ImageView) findViewById(R.id.avatar);
    
            //App          ,    
            sIdlingResource.increment();
            //        
            Glide.with(this).load("https://avatars2.githubusercontent.com/u/2297803?v=3&s=460").into(new GlideDrawableImageViewTarget(mAvatar) {
                @Override
                public void onResourceReady(GlideDrawable resource,
                                            GlideAnimation super GlideDrawable> animation) {
                    super.onResourceReady(resource, animation);
                    //     , App       
                    sIdlingResource.decrement();
                }
            });
        }
    }

    SimpleIdlingResourceコード
    public final class SimpleIdlingResource implements IdlingResource {
    
        private final AtomicInteger counter = new AtomicInteger(0);
    
        private volatile ResourceCallback resourceCallback;
    
        @Override
        public String getName() {
            return "SimpleIdlingResource";
        }
    
        /**
         *   counter    0,         
         */    
        @Override
        public boolean isIdleNow() {
            return counter.get() == 0;
        }
    
        @Override
        public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
            this.resourceCallback = resourceCallback;
        }
    
        /**
         * counter      
         */
        public void increment() {
            counter.getAndIncrement();
        }
    
        /**
         *counter      
         */
        public void decrement() {
            int counterVal = counter.decrementAndGet();
            if (counterVal == 0) {
                //   onTransitionToIdle()  ,  Espresso,       。
                if (null != resourceCallback) {
                    resourceCallback.onTransitionToIdle();
                }
            }
    
            if (counterVal < 0) {
                throw new IllegalArgumentException("Counter has been corrupted!");
            }
        }
    }

    IdlingResourceTestコード
    @RunWith(AndroidJUnit4.class)
    public class IdlingResourceTest {
    
        @Rule
        public ActivityTestRule rule=new ActivityTestRule<>(AvatarActivity.class,true);
    
        @Before
        public void registerIdlingResource(){
            Espresso.registerIdlingResources(rule.getActivity().sIdlingResource);
        }
    
        @Test
        public void avatarDisplayed(){
            onView(withId(R.id.avatar)).check(matches(isDisplayed()));
        }
    
        @After
        public void unregisterIdlingResource() {
            Espresso.unregisterIdlingResources(
                    rule.getActivity().sIdlingResource);
        }
    
    }

    また、Espressoは実現したCountingIdlingResourceクラスを提供しているので、特に必要がなければCountingIdlingResourceをそのまま使えばよい.
    2.カスタムEspresso matcherを作成する
    現在、Espressoが提供している方法は、下図に示すように、基本的にテストのニーズを満たすことができます.
    カスタムビューのカスタム属性をテストする必要がある場合は、カスタムMatcherを作成できます.
       public static Matcher isMoved() {
            return new TypeSafeMatcher() {
                @Override
                public void describeTo(Description description) {
                    description.appendText("is moved");
                }
    
                @Override
                public boolean matchesSafely(View view) {
                    return ((CustomView)view).isMoved();
                }
            };
        }

    3.アニメーションの処理方法
  • システムアニメーション:
  • アニメーションスレッドの実行中にEspressoテストに与える影響を避けるために、システムアニメーションを閉じることを強くお勧めします.図に示すように、
  • カスタムアニメーション:
  • カスタムアニメーションの場合、開発者は以下のコードを使用してアニメーションのオンとオフを制御できます.
    この方法はEspressoテストだけでなく、システムアニメーションのスイッチングイベントをリスニングすることができます.
       boolean animationEnabled=true;
    
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1){
            try {
                if (Settings.Global.getFloat(getContentResolver(),Settings.Global.ANIMATOR_DURATION_SCALE)==0.0f){
                    animationEnabled=false;
                }
            } catch (Settings.SettingNotFoundException e) {
                e.printStackTrace();
            }
        }

    4.優雅なIntentTest
  • 一般的な方式
  •    @Rule
        public ActivityTestRule rule=new ActivityTestRule(MainActivity.class){
    
            @Override
            protected Intent getActivityIntent() {
                Intent result=new Intent(...);
                result.putExtra(...)
                return result;
            }
        };

    通常の方法を使用する場合、ユニットテストクラスの下のすべてのテストは、Intentに基づいて起動されたActivityに基づいており、これは明らかに柔軟ではありません.
  • エレガントな方法
  •   @Rule
        public ActivityTestRule rule=new ActivityTestRule(MainActivity.class,true,false);//            Activity
    
        public void myTest(){
            Intent result=new Intent(...);
            result.putExtra(...);
            rule.launchActivity(result);
        }

    5.テストの独立性を保証する
    通常、Espressoを使用してUIテストを行う場合、ネットワークやリモートのサービスに関連するものをテストすることは望ましくありません.そのため、Espresso Intent、Mockito for mocking、注射、Dagger 2に依存することができます.要するに、UIレベルではないコンテンツをできるだけ分離します.
    たとえば、あるボタンのクリックイベントをテストする必要がありますが、そのボタンをクリックすると、別のActivityにジャンプしますが、他のActivityをテストしたくない場合は、ここでIntentをブロックすることで解決できます.
    まず、IntentTest依存ライブラリを追加する必要があります.
    androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'

    InterceptIntentTestサンプルコード
    @RunWith(AndroidJUnit4.class)
    public class InterceptIntentTest {
    
        @Rule
        public IntentsTestRule rule=new IntentsTestRule<>(MainActivity.class);
    
        @Before
        public void stubCameraIntent(){
            //     ActivityResult
            Instrumentation.ActivityResult result = createImageCaptureActivityResultStub();
            //  MediaStore.ACTION_IMAGE_CAPTURE,       result
            intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)).respondWith(result);
        }
    
        @Test
        public void takePhoto_cameraIsLaunched(){
            onView(withId(R.id.button_take_photo)).perform(click());
            intended(hasAction(MediaStore.ACTION_IMAGE_CAPTURE));
            ...
        }
    
        private Instrumentation.ActivityResult createImageCaptureActivityResultStub() {
            // Create the ActivityResult, with a null Intent since we do not want to return any data
            // back to the Activity.
            return new Instrumentation.ActivityResult(Activity.RESULT_OK, null);
        }
    
    }

    6.testコードの直接コピーと貼り付けを避ける
    簡単な例を挙げる.
    onData(allOf(is(instanceOf(Map.class)),hasEntry(equalTo("STR"),is("item:50")))).perform(click());

    以上のコードはマッチングリストの条件に合致するitemであり、クリックイベントを実行し、テストも正常であり、同様にこのコードも他のテスト方法にコピーされて使用されている.この場合、adapterのデータソースがcursorまたは他に変更された場合、悲しいことに、多くの場所を修正する必要があると考えてみてください.明らかに、これは合格したCV戦士ではありません.
    以前のコードを改装する必要があります
       @Test
        public void myTest(){
            onData(withItemContent("item:50")).perform(click());
        }
    
        public static Matcher extends Object> withItemContent(String expectedText) {
            ....
        }

    簡単で、変化する可能性のある部分を抽出するだけでいいです.
    7.Viewの場所をテストする方法
    図に示すように、
    8.カスタムエラーログ
    図:
    関心のあるログ情報だけを表示したい場合は、FailureHandlerをカスタマイズできます.
    private static class CustomFailureHandler implements FailureHandler {
        private final FailureHandler delegate;
    
        public CustomFailureHandler(Context targetContext) {
            delegate = new DefaultFailureHandler(targetContext);
        }
    
        @Override
        public void handle(Throwable error, Matcher viewMatcher) {
            try {
                delegate.handle(error, viewMatcher);
            } catch (NoMatchingViewException e) {
                throw new MySpecialException(e);
            }
        }
    }
    @Override
    public void setUp() throws Exception {
        super.setUp();
        getActivity();
        setFailureHandler(new CustomFailureHandler(getInstrumentation()
                                                  .getTargetContext()));
    }

    著者:yonglan.whl
    原文を読む
    本文は雲栖コミュニティのオリジナル内容で、許可を得ずに転載してはならない.