automated testing with espresso2.x
TRANSCRIPT
![Page 1: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/1.jpg)
Espresso 2.xAutomated testing with
![Page 2: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/2.jpg)
Tatsuya MAKI Android Application Developer
![Page 3: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/3.jpg)
Ⅰなぜやるのか
Ⅱ Ⅲどこに使うか 基本のおさらい
Ⅳ5つのポイント
![Page 4: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/4.jpg)
Ⅰなぜやるのか
![Page 5: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/5.jpg)
要件定義 設計 リリース開発 テスト
![Page 6: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/6.jpg)
要件定義 設計 リリース開発 テスト
プロセスを効率化すれば改善サイクルが速くなる。
![Page 7: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/7.jpg)
多くのプロセスは自動化しにくい。
要件定義 設計 リリース開発 テスト
![Page 8: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/8.jpg)
テストやリリースは自動化しやすい。
要件定義 設計 リリース開発 テスト
![Page 9: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/9.jpg)
開発サイクルを効率的に回すためにテストの自動化が重要。
![Page 10: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/10.jpg)
Ⅱどこに使うか
![Page 11: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/11.jpg)
Person Presentation Layer
Domain Layer
Web API
SQLite Database
Shared Preferences
Data Layer
![Page 12: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/12.jpg)
Person Presentation Layer
Domain Layer
Web API
SQLite Database
Shared Preferences
Data Layer
多くのアプリケーションは多層アーキテクチャで構成される。
![Page 13: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/13.jpg)
Person Presentation Layer
Domain Layer
Web API
SQLite Database
Shared Preferences
Data Layer
結合テストが必要なレイヤはEspressoが適する。
![Page 14: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/14.jpg)
Person Presentation Layer
Domain Layer
Web API
SQLite Database
Shared Preferences
Data Layer
単体テストで十分なレイヤはRobolectricが適する。
![Page 15: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/15.jpg)
レイヤに応じた適切なフレームワークを選択する。
![Page 16: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/16.jpg)
Ⅲ基本のおさらい
![Page 17: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/17.jpg)
@RunWith(AndroidJUnit4.class)public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, true, false); @Test public void shouldLaunchActivity() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); final Activity activity = mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.my_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); }}
![Page 18: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/18.jpg)
@RunWith(AndroidJUnit4.class)public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, true, false); @Test public void shouldLaunchActivity() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); final Activity activity = mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.my_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); }}
テストランナーにAndroidJUnit4を指定。
![Page 19: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/19.jpg)
@RunWith(AndroidJUnit4.class)public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, true, false); @Test public void shouldLaunchActivity() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); final Activity activity = mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.my_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); }}
ActivityTestRuleでテスト対象を指定。
![Page 20: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/20.jpg)
@RunWith(AndroidJUnit4.class)public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, true, false); @Test public void shouldLaunchActivity() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); final Activity activity = mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.my_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); }}
Activityの起動はActivityTestRule経由。
![Page 21: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/21.jpg)
@RunWith(AndroidJUnit4.class)public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, true, false); @Test public void shouldLaunchActivity() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); final Activity activity = mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.my_view)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); }}
UI取得やUI操作はEspresso経由。
![Page 22: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/22.jpg)
Espressoでテストを書くハードルはそこまで高くない。
![Page 23: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/23.jpg)
Ⅳ5つのポイント
![Page 24: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/24.jpg)
ページオブジェクトを導入する。
外部依存を可能な限り排除する。
インスタンスを変更可能にする。1
2
3
4
5 EspressoのAPIをラップする。
Sleepによる待機を避ける。
![Page 25: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/25.jpg)
ページオブジェクトを導入する。
外部依存を可能な限り排除する。
インスタンスを変更可能にする。1
2
3
4
5 EspressoのAPIをラップする。
非同期処理の待機にSleepを避ける。
![Page 26: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/26.jpg)
public class VolleyProvider { private static RequestQueue sRequestQueue; private static ImageLoader sImageLoader; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } return sRequestQueue; } public static ImageLoader getImageLoader(Context context) { if (sImageLoader == null) { final RequestQueue queue = getRequestQueue(context); sImageLoader = new ImageLoader(queue, new LruBitmapCache()); } return sImageLoader; }}
![Page 27: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/27.jpg)
public class VolleyProvider { private static RequestQueue sRequestQueue; private static ImageLoader sImageLoader; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } return sRequestQueue; } public static ImageLoader getImageLoader(Context context) { if (sImageLoader == null) { final RequestQueue queue = getRequestQueue(context); sImageLoader = new ImageLoader(queue, new LruBitmapCache()); } return sImageLoader; }}
RequestQueueのインスタンスが変更不可能。
![Page 28: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/28.jpg)
public class VolleyProvider { private static RequestQueue sRequestQueue; private static ImageLoader sImageLoader; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } return sRequestQueue; } public static ImageLoader getImageLoader(Context context) { if (sImageLoader == null) { final RequestQueue queue = getRequestQueue(context); sImageLoader = new ImageLoader(queue, new LruBitmapCache()); } return sImageLoader; }}
ImageLoaderのインスタンスも変更不可能。
![Page 29: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/29.jpg)
依存するインスタンスの振舞に応じたテストができない。
![Page 30: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/30.jpg)
依存性注入の仕組みを導入する。
![Page 31: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/31.jpg)
public class VolleyProvider { private static RequestQueue sRequestQueue; private static ImageLoader sImageLoader; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } return sRequestQueue; } public static ImageLoader getImageLoader(Context context) { if (sImageLoader == null) { final RequestQueue queue = getRequestQueue(context); sImageLoader = new ImageLoader(queue, new BitmapCache()); } return sImageLoader; } static void setRequestQueue(RequestQueue queue) { sRequestQueue = queue; } static void setImageLoader(ImageLoader loader) { sImageLoader = loader; }}
![Page 32: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/32.jpg)
public class VolleyProvider { private static RequestQueue sRequestQueue; private static ImageLoader sImageLoader; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } return sRequestQueue; } public static ImageLoader getImageLoader(Context context) { if (sImageLoader == null) { final RequestQueue queue = getRequestQueue(context); sImageLoader = new ImageLoader(queue, new BitmapCache()); } return sImageLoader; } static void setRequestQueue(RequestQueue queue) { sRequestQueue = queue; } static void setImageLoader(ImageLoader loader) { sImageLoader = loader; }}
RequestQueueのインスタンスが変更可能。
![Page 33: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/33.jpg)
public class VolleyProvider { private static RequestQueue sRequestQueue; private static ImageLoader sImageLoader; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } return sRequestQueue; } public static ImageLoader getImageLoader(Context context) { if (sImageLoader == null) { final RequestQueue queue = getRequestQueue(context); sImageLoader = new ImageLoader(queue, new BitmapCache()); } return sImageLoader; } static void setRequestQueue(RequestQueue queue) { sRequestQueue = queue; } static void setImageLoader(ImageLoader loader) { sImageLoader = loader; }}
ImageLoaderのインスタンスが変更可能。
![Page 34: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/34.jpg)
インスタンスを変更可能な実装にしてメンテナビリティを高める。
![Page 35: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/35.jpg)
ページオブジェクトを導入する。
外部依存を可能な限り排除する。
インスタンスを変更可能にする。1
2
3
4
5 EspressoのAPIをラップする。
Sleepによる待機を避ける。
![Page 36: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/36.jpg)
Person Presentation Layer
Domain Layer
Web API
SQLite Database
Shared Preferences
Data Layer
![Page 37: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/37.jpg)
Person Presentation Layer
Domain Layer
Web API
SQLite Database
Shared Preferences
Data Layer
![Page 38: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/38.jpg)
@Testpublic void shouldShowLoadingViewWhenRequestIsLoading() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowContentWhenRequestSucceeded() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)));}
![Page 39: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/39.jpg)
@Testpublic void shouldShowLoadingViewWhenRequestIsLoading() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowFailureViewWhenRequestIsFailed() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)));}
読込中の表示を検証したい。
![Page 40: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/40.jpg)
@Testpublic void shouldShowLoadingViewWhenRequestIsLoading() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowFailureViewWhenRequestIsFailed() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)));}
読込成功の表示を検証したい。
![Page 41: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/41.jpg)
@Testpublic void shouldShowLoadingViewWhenRequestIsLoading() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
@Testpublic void shouldShowFailureViewWhenRequestIsFailed() { launchActivity(); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)));}
読込失敗の表示を検証したい。
![Page 42: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/42.jpg)
外部に依存する振舞をテストし切れない。
![Page 43: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/43.jpg)
テスト用のインスタンスに変更して振舞を制御する。
![Page 44: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/44.jpg)
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { final NetworkDispatcher dispatcher = mRequestQueue.getNetworkDispatcher(); dispatcher.append( new BasicRequestMatcher.Builder() .setMethodMatcher(MethodMatcher.GET) .setUrlPattern("^https://ajax.googleapis.com/ajax/services/feed/load.+") .build(), new NetworkResponseBuilder() .setStatusCode(StatusCode.OK) .addHeader("Content-Type", "application/json") .setBody(mAssetReader.read("feed_load_success_10.json")) .build() ); mRequestQueue.resume(); final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
![Page 45: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/45.jpg)
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { final NetworkDispatcher dispatcher = mRequestQueue.getNetworkDispatcher(); dispatcher.append( new BasicRequestMatcher.Builder() .setMethodMatcher(MethodMatcher.GET) .setUrlPattern("^https://ajax.googleapis.com/ajax/services/feed/load.+") .build(), new NetworkResponseBuilder() .setStatusCode(StatusCode.OK) .addHeader("Content-Type", "application/json") .setBody(mAssetReader.read("feed_load_success_10.json")) .build() ); mRequestQueue.resume(); final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
外部に依存する振舞を事前に変更しておく。
![Page 46: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/46.jpg)
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { final NetworkDispatcher dispatcher = mRequestQueue.getNetworkDispatcher(); dispatcher.append( new BasicRequestMatcher.Builder() .setMethodMatcher(MethodMatcher.GET) .setUrlPattern("^https://ajax.googleapis.com/ajax/services/feed/load.+") .build(), new NetworkResponseBuilder() .setStatusCode(StatusCode.OK) .addHeader("Content-Type", "application/json") .setBody(mAssetReader.read("feed_load_success_10.json")) .build() ); mRequestQueue.resume(); final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
マッチさせるリクエストのパターンを指定。
![Page 47: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/47.jpg)
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { final NetworkDispatcher dispatcher = mRequestQueue.getNetworkDispatcher(); dispatcher.append( new BasicRequestMatcher.Builder() .setMethodMatcher(MethodMatcher.GET) .setUrlPattern("^https://ajax.googleapis.com/ajax/services/feed/load.+") .build(), new NetworkResponseBuilder() .setStatusCode(StatusCode.OK) .addHeader("Content-Type", "application/json") .setBody(mAssetReader.read("feed_load_success_10.json")) .build() ); mRequestQueue.resume(); final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
パターンにマッチした際のレスポンスを指定。
![Page 48: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/48.jpg)
@Testpublic void shouldShowSuccessViewWhenRequestIsSucceeded() { final NetworkDispatcher dispatcher = mRequestQueue.getNetworkDispatcher(); dispatcher.append( new BasicRequestMatcher.Builder() .setMethodMatcher(MethodMatcher.GET) .setUrlPattern("^https://ajax.googleapis.com/ajax/services/feed/load.+") .build(), new NetworkResponseBuilder() .setStatusCode(StatusCode.OK) .addHeader("Content-Type", "application/json") .setBody(mAssetReader.read("feed_load_success_10.json")) .build() ); mRequestQueue.resume(); final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
事前処理が終わったらテストを実行する。
![Page 49: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/49.jpg)
外部依存を排除できる実装にしてテスタビリティを高める。
![Page 50: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/50.jpg)
ページオブジェクトを導入する。
外部依存を可能な限り排除する。
インスタンスを変更可能にする。1
2
3
4
5 EspressoのAPIをラップする。
Sleepによる待機を避ける。
![Page 51: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/51.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() throws InterruptedException { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); Thread.sleep(5000); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
![Page 52: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/52.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() throws InterruptedException { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); Thread.sleep(5000); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
該当のActivityを起動する。
![Page 53: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/53.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() throws InterruptedException { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); Thread.sleep(5000); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
非同期処理が完了するまで待機する。
![Page 54: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/54.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() throws InterruptedException { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); Thread.sleep(5000); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
非同期処理完了時の表示を検証する。
![Page 55: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/55.jpg)
Sleepを含むテストは壊れやすくスローテストに陥りやすい。
![Page 56: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/56.jpg)
非同期処理の完了待ちにIdlingResourceが使えるようにする。
![Page 57: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/57.jpg)
public class TestRequestQueue extends RequestQueue { private List<RequestAddedListener> mAddedListeners = new ArrayList<>(); @Override public <T> Request<T> add(Request<T> request) { synchronized (mAddedListeners) { for (RequestAddedListener<T> listener : mAddedListeners) { listener.onRequestAdded(request); } } return super.add(request); } public <T> void addRequestAddedListener(RequestAddedListener<T> listener) { synchronized (mAddedListeners) { mAddedListeners.add(listener); } } public <T> void removeRequestAddedListener(RequestAddedListener<T> listener) { synchronized (mAddedListeners) { mAddedListeners.remove(listener); } } public interface RequestAddedListener<T> { void onRequestAdded(Request<T> request); }}
![Page 58: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/58.jpg)
public class TestRequestQueue extends RequestQueue { private List<RequestAddedListener> mAddedListeners = new ArrayList<>(); @Override public <T> Request<T> add(Request<T> request) { synchronized (mAddedListeners) { for (RequestAddedListener<T> listener : mAddedListeners) { listener.onRequestAdded(request); } } return super.add(request); } public <T> void addRequestAddedListener(RequestAddedListener<T> listener) { synchronized (mAddedListeners) { mAddedListeners.add(listener); } } public <T> void removeRequestAddedListener(RequestAddedListener<T> listener) { synchronized (mAddedListeners) { mAddedListeners.remove(listener); } } public interface RequestAddedListener<T> { void onRequestAdded(Request<T> request); }}
非同期処理の開始と完了を検知可能にする。
![Page 59: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/59.jpg)
public class RequestQueueListener implements RequestAddedListener, RequestFinishedListener { private final CountingIdlingResource mIdlingResource; public RequestQueueListener(CountingIdlingResource idlingResource) { mIdlingResource = idlingResource; } @Override public void onRequestAdded(Request request) { mIdlingResource.increment(); } @Override public void onRequestFinished(Request request) { mIdlingResource.decrement(); }}
![Page 60: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/60.jpg)
public class RequestQueueListener implements RequestAddedListener, RequestFinishedListener { private final CountingIdlingResource mIdlingResource; public RequestQueueListener(CountingIdlingResource idlingResource) { mIdlingResource = idlingResource; } @Override public void onRequestAdded(Request request) { mIdlingResource.increment(); } @Override public void onRequestFinished(Request request) { mIdlingResource.decrement(); }}
非同期処理の開始をIdlingResourceに通知。
![Page 61: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/61.jpg)
public class RequestQueueListener implements RequestAddedListener, RequestFinishedListener { private final CountingIdlingResource mIdlingResource; public RequestQueueListener(CountingIdlingResource idlingResource) { mIdlingResource = idlingResource; } @Override public void onRequestAdded(Request request) { mIdlingResource.increment(); } @Override public void onRequestFinished(Request request) { mIdlingResource.decrement(); }}
非同期処理の完了をIdlingResourceに通知。
![Page 62: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/62.jpg)
@Beforepublic void setUp() { Context context = InstrumentationRegistry.getContext(); mRequestQueue = (MockRequestQueue) VolleyProvider.getRequestQueue(context); mIdlingResource = new CountingIdlingResource(RequestQueue.class.getName()); registerIdlingResources(mIdlingResource); RequestQueueListener queueListener = new RequestQueueListener(mIdlingResource); mRequestQueue.addRequestAddedListener(queueListener); mRequestQueue.addRequestFinishedListener(queueListener);}
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { ... }@Afterpublic void tearDown() { unregisterIdlingResources(mIdlingResource);}
![Page 63: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/63.jpg)
@Beforepublic void setUp() { Context context = InstrumentationRegistry.getContext(); mRequestQueue = (MockRequestQueue) VolleyProvider.getRequestQueue(context); mIdlingResource = new CountingIdlingResource(RequestQueue.class.getName()); registerIdlingResources(mIdlingResource); RequestQueueListener queueListener = new RequestQueueListener(mIdlingResource); mRequestQueue.addRequestAddedListener(queueListener); mRequestQueue.addRequestFinishedListener(queueListener);}
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { ... }@Afterpublic void tearDown() { unregisterIdlingResources(mIdlingResource);}
事前処理でIdlingResourceを登録する。
![Page 64: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/64.jpg)
@Beforepublic void setUp() { Context context = InstrumentationRegistry.getContext(); mRequestQueue = (MockRequestQueue) VolleyProvider.getRequestQueue(context); mIdlingResource = new CountingIdlingResource(RequestQueue.class.getName()); registerIdlingResources(mIdlingResource); RequestQueueListener queueListener = new RequestQueueListener(mIdlingResource); mRequestQueue.addRequestAddedListener(queueListener); mRequestQueue.addRequestFinishedListener(queueListener);}
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { ... }@Afterpublic void tearDown() { unregisterIdlingResources(mIdlingResource);}
事後処理でIdlingResourceを解除する。
![Page 65: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/65.jpg)
Sleepを可能な限り避けテストのメンテナビリティを高める。
![Page 66: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/66.jpg)
ページオブジェクトを導入する。
外部依存を可能な限り排除する。
インスタンスを変更可能にする。1
2
3
4
5 EspressoのAPIをラップする。
Sleepによる待機を避ける。
![Page 67: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/67.jpg)
TestCase UI
TestCase
TestCaseonView()
onView()
onView()
![Page 68: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/68.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
![Page 69: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/69.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
UI操作を直接記述する。
![Page 70: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/70.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.navigation_icon)).perform(click()); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
UI取得を直接記述する。
![Page 71: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/71.jpg)
UI操作やUI取得を直接記述するとUI変更に弱い。
![Page 72: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/72.jpg)
ページオブジェクトを導入しUI操作を集約する。
![Page 73: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/73.jpg)
TestCase PageObject
TestCase
TestCasefindLoadingView()
findLoadingView()
findLoadingView()
UIonView()
![Page 74: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/74.jpg)
public class EntryListPage { public EntryListPage() { } public void clickNavigationIcon() { onView(withId(R.id.navigation_icon)).perform(click()); } public void pullToRefresh() { onView(withId(android.R.id.content)).perform(swipeDown()); } public ViewInteraction findLoadingView() { return onView(withId(R.id.entry_list_loading)); } public ViewInteraction findSuccessView() { return onView(withId(R.id.entry_list_success)); } public ViewInteraction findFailureView() { return onView(withId(R.id.entry_list_failure)); }}
![Page 75: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/75.jpg)
public class EntryListPage { public EntryListPage() { } public void clickNavigationIcon() { onView(withId(R.id.navigation_icon)).perform(click()); } public void pullToRefresh() { onView(withId(android.R.id.content)).perform(swipeDown()); } public ViewInteraction findLoadingView() { return onView(withId(R.id.entry_list_loading)); } public ViewInteraction findSuccessView() { return onView(withId(R.id.entry_list_success)); } public ViewInteraction findFailureView() { return onView(withId(R.id.entry_list_failure)); }}
UI操作をページオブジェクトに集約する。
![Page 76: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/76.jpg)
public class EntryListPage { public EntryListPage() { } public void clickNavigationIcon() { onView(withId(R.id.navigation_icon)).perform(click()); } public void pullToRefresh() { onView(withId(android.R.id.content)).perform(swipeDown()); } public ViewInteraction findLoadingView() { return onView(withId(R.id.entry_list_loading)); } public ViewInteraction findSuccessView() { return onView(withId(R.id.entry_list_success)); } public ViewInteraction findFailureView() { return onView(withId(R.id.entry_list_failure)); }}
UI取得もページオブジェクトに集約する。
![Page 77: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/77.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); final EntryListPage page = new EntryListPage(); page.clickNavigationIcon(); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); }
![Page 78: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/78.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); final EntryListPage page = new EntryListPage(); page.clickNavigationIcon(); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); }
UI操作はページオブジェクトを経由する。
![Page 79: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/79.jpg)
@Testpublic void shouldShowContentWhenRequestSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = MainActivity.createIntent(context); mActivityRule.launchActivity(intent); final EntryListPage page = new EntryListPage(); page.clickNavigationIcon(); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); page.findFailureView() .check(matches(withEffectiveVisibility(Visibility.GONE))); }
UI取得もページオブジェクトを経由する。
![Page 80: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/80.jpg)
ページオブジェクトを導入してテストのメンテナビリティを高める。
![Page 81: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/81.jpg)
ページオブジェクトを導入する。
外部依存を可能な限り排除する。
インスタンスを変更可能にする。1
2
3
4
5 EspressoのAPIをラップする。
Sleepによる待機を避ける。
![Page 82: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/82.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_refresh)).perform(click()); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
![Page 83: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/83.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_refresh)).perform(click()); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
該当IDのViewをクリックする。
![Page 84: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/84.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onView(withId(R.id.entry_list_refresh)).perform(click()); onView(withId(R.id.entry_list_loading)) .check(matches(withEffectiveVisibility(Visibility.GONE))); onView(withId(R.id.entry_list_success)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))); onView(withId(R.id.entry_list_failure)) .check(matches(withEffectiveVisibility(Visibility.GONE)));}
Viewの可視性を検証する。
![Page 85: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/85.jpg)
記述が冗長で可読性も低い。
![Page 86: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/86.jpg)
EspressoのAPIをラップしシンプルな記述を可能にする。
![Page 87: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/87.jpg)
public static ViewInteraction onViewById(int id) { return onView(withId(id));}public static ViewAssertion isVisible() { return hasVisibility(ViewMatchers.Visibility.VISIBLE);}public static ViewAssertion isInvisible() { return hasVisibility(ViewMatchers.Visibility.INVISIBLE);}public static ViewAssertion isGone() { return hasVisibility(ViewMatchers.Visibility.GONE);}private static ViewAssertion hasVisibility(ViewMatchers.Visibility visibility) { return matches(withEffectiveVisibility(visibility));}
![Page 88: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/88.jpg)
public static ViewInteraction onViewById(int id) { return onView(withId(id));}public static ViewAssertion isVisible() { return hasVisibility(ViewMatchers.Visibility.VISIBLE);}public static ViewAssertion isInvisible() { return hasVisibility(ViewMatchers.Visibility.INVISIBLE);}public static ViewAssertion isGone() { return hasVisibility(ViewMatchers.Visibility.GONE);}private static ViewAssertion hasVisibility(ViewMatchers.Visibility visibility) { return matches(withEffectiveVisibility(visibility));}
IDからViewInteractionを取得する。
![Page 89: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/89.jpg)
public static ViewInteraction onViewById(int id) { return onView(withId(id));}public static ViewAssertion isVisible() { return hasVisibility(ViewMatchers.Visibility.VISIBLE);}public static ViewAssertion isInvisible() { return hasVisibility(ViewMatchers.Visibility.INVISIBLE);}public static ViewAssertion isGone() { return hasVisibility(ViewMatchers.Visibility.GONE);}private static ViewAssertion hasVisibility(ViewMatchers.Visibility visibility) { return matches(withEffectiveVisibility(visibility));}
Viewの可視性を検証する。
![Page 90: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/90.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onViewById(R.id.entry_list_refresh)).perform(click()); onViewById(R.id.entry_list_loading)).check(isGone()); onViewById(R.id.entry_list_success)).check(isVisible()); onViewById(R.id.entry_list_failure)).check(isGone());}
![Page 91: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/91.jpg)
@Testpublic void shouldShowContentWhenRequestIsSucceeded() { final Context context = InstrumentationRegistry.getTargetContext(); final Intent intent = EntryListActivity.createIntent(context); mActivityRule.launchActivity(intent); onViewById(R.id.entry_list_refresh)).perform(click()); onViewById(R.id.entry_list_loading)).check(isGone()); onViewById(R.id.entry_list_success)).check(isVisible()); onViewById(R.id.entry_list_failure)).check(isGone());}
可読性が圧倒的に向上する。
![Page 92: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/92.jpg)
EspressoのAPIをラップして可読性を高める。
![Page 93: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/93.jpg)
まとめ
![Page 94: Automated testing with Espresso2.x](https://reader036.vdocuments.mx/reader036/viewer/2022062420/55c430c9bb61ebc1628b47a5/html5/thumbnails/94.jpg)
1.開発サイクルを効率的に回すためにテストの自動化が重要。
2.レイヤに応じた適切なフレームワークを選択する。
3. Espressoでテストを書くハードルはそこまで高くない。
4. 継続しやすいテストを書く基本的なポイントを押さえる。