
Recyclerviewãšã¯äœã§ããïŒ ããã¯ããªã¹ããäœæãããã³ã³ããŒãã³ãã§ãã åãªã¹ããã¹ã¯ããŒã«ããããã¢ã€ãã ãè¿œå ããããåé€ããããå€æŽãããã§ããŸãã èŠçŽ ã¯ãä»»æã®æ©èœåäœã§ãã ããšãã°ãã³ã¡ã³ããšãã¿ã³ãå ¥åãããã£ãŒã«ããæã€ãŠãŒã¶ãŒã®ãªã¹ããäœæããŸãã ã³ã¡ã³ããå ¥åããããã¿ã³ãæŒããããšããã«ããµãŒããŒã«éä¿¡ãããŸãã ããã§ãã·ã¹ãã ã¯ã¢ã€ãã ãã¢ããã°ã¬ãŒããŸãã¯åé€ã§ããŸãã
èŠçŽ ã«ã¯å€ãã®ã³ã³ãããŒã«ïŒãã¿ã³ãªã©ïŒãå«ããããšãã§ãããããèŠçŽ ã®ãã¹ãŠã®æ©èœããã¹ãã§ã«ããŒããå¿ èŠããããŸãã 次ã«ãRecyclerviewã®äŸ¿å©ãªãŠãŒãã£ãªãã£ã玹ä»ããŸãã
äŸ
äŸãšããŠãåç©ã®ãªã¹ãã衚瀺ããç°¡åãªã¢ããªã±ãŒã·ã§ã³ãåãäžããŸãã ãªã¹ãããŒã¿ã¯ãåãã¿ã€ãã®ãªããžã§ã¯ãã®é åã§ãã ããã§ãç§ãã¡ã¯äœãèŠãŸããïŒ
[ ... { "type": "PREDATOR", "name": "", "image_url": "https://www.myplanet-ua.com/wp-content/uploads/2017/05/%D0%B2%D0%B8%D0%B4%D1%8B-%D1%82%D0%B8%D0%B3%D1%80%D0%BE%D0%B2.jpg" }, { "type": "HERBIVORE", "name": "", "image_url": "https://cs2.livemaster.ru/storage/b2/40/b9d72365ffc02131ea60420cdc0s--kukly-i-igrushki-suslik-bejli-igrushka-iz-shersti.jpg" }, .... ]
èŠçŽ ã¯ä»¥äžã§æ§æãããŸãïŒ
- åç©ã®çš®-æé£è ïŒæé£è ïŒããã³èé£åç©ïŒèé£åç©ïŒã ããã«å¿ããŠãåç©ã«ã¯1ã€ãŸãã¯å¥ã®ã¢ã€ã³ã³ãå²ãåœãŠãããŸãã
- åç©ã®ååïŒãã©ãããªããºããªã©ïŒã
- åç©ã®ç»åãžã®ãªã³ã¯ã
ãŠãŒã¶ãŒã«ã¯æ¬¡ã衚瀺ãããŸãã

[åé€]ãã¿ã³ãã¯ãªãã¯ãããšããªã¹ãããç»åãæ¶ããŸãã åç©èªäœãã¯ãªãã¯ãããšããã詳现ãªèª¬æãéããŸãã
ãã¹ãã«åæ ŒããŸãã
- ã¢ã€ãã ãã©ã®ããã«åé€ãããããèŠãŠã¿ãŸãããã
- ã¢ã€ã³ã³ãåç©ã®çš®ã«å¯Ÿå¿ããŠãããã©ããã確èªããŸãïŒç·ã®ã¹ãã€ãªãŒ-èé£åç©ãèµ€ã®ã¹ãã€ãªãŒ-æé£è ïŒã
- ååãæå®ãããååãšäžèŽãããã©ããã確èªããŸãã
䟿å®äžã以äžã®ç»åã§ãã¹ãæžã¿ã®é åã«ããŒã¯ãä»ããŸãã

ãã®ãããè£å©ã¯ã©ã¹ã®äœæãéå§ããŸãã
DrawableMatcher
æåã®éèŠãªãã«ããŒã¯ã©ã¹ã¯ãç»åãããŒã«ã«ãªãœãŒã¹ãšäžèŽãããã©ããã確èªããããšã§ãã ã¿ã€ããã£ãŒã«ãïŒãµãŒããŒããã®å¿çïŒãšããŒã«ã«ãªãœãŒã¹ã®ååã®å¯Ÿå¿ã¯æ¬¡ã®ãšããã§ãã
ãã¬ãã¿ãŒ-ic_sentiment_very_dissatisfied_red_24dp
HERBIVORE-ic_sentiment_very_satisfied_green_24dp
äŸïŒãã®ã¿ã¹ã¯ã§ã¯ãcapybaraãèé£åç©ã§ããããšã確èªãããŠãããããic_sentiment_very_satisfied_green_24dpã®å€ã¯ãivAnimalTypeèå¥åãæã€imageViewèŠçŽ ã§èšå®ããå¿ èŠããããŸãã
å®éãåç©ã®çš®é¡ã«å¿ããŠã確ç«ããããªãœãŒã¹ã®é©åæ§ã確èªããå¿ èŠããããŸãã ãã®ããã®æšæºããŒã«ãèŠã€ãããªãã£ããããMatcherãäœæããå¿ èŠããããŸãããããã¯ãéä¿¡ãããèå¥åã«æºæ ããŠãããã©ããimageViewèŠçŽ ã®èæ¯ããããã£ããã§ãã¯ããŸãã
å°ããªäºçŽ
ãããã£ãŒã¯ããŠãŒã¶ãŒãã¥ãŒã®ç¹å®ã®ããããã£ããã§ãã¯ããŠãæž¡ããããã©ã¡ãŒã¿ãŒã«æºæ ããŠãããã©ããã確èªããããã«æããã䜿çšãããŸãã
äŸïŒãã¢ãæããç¬èªã®ã«ã¹ã¿ã ãã¥ãŒãäœæããŸããã éããç¶æ ãšéããç¶æ ã®2ã€ã®ç¶æ ããããŸãã ãã¢ã®æäœã確èªããããã«ãç¬èªã®Matcherãäœæã§ããŸãã ãã³ãã«ãåããåŸããã¢ãéããç¶æ ããéããç¶æ ã«ãªãããšã«åæãããã¹ãã§ã¯ãã³ãã«ãåŒããç¬èªã®ãããã£ãŒã䜿çšããŠãæ¬åœã«éããŠããããšã確èªããŸãã
ãã®çš®ã®ãããã£ãŒãäœæããã«ã¯ãæšæºã¯ã©ã¹ããã±ãŒãžorg.hamcrest-TypeSafeMatcherã䜿çšããŸãã 詳现ã¯ãã¡ããã芧ãã ããã
ãã®ã¯ã©ã¹ã¯ããŠãŒã¶ãŒãã¥ãŒã¿ã€ãïŒimageViewãviewãªã©ïŒãæã€æ±çšã¯ã©ã¹ã§ãã 2ã€ã®äž»ãªæ¹æ³ãæäŸããŸãã
- matchesSafelyã¯ãã³ã³ãã©ã€ã¢ã³ã¹ãã§ãã¯ã«åæ Œããã«ãŒãã¡ãœããã§ãã ãžã§ããªãã¯ã¯ã©ã¹åã®ãªããžã§ã¯ãããã®ãã©ã¡ãŒã¿ãŒã«æž¡ãããŸãã
- describeTo-ãªããžã§ã¯ããèªèº«ãèšè¿°ããæ©èœã®ããã®é¢æ°ã æªç¥ã®äžèŽãããå Žåã«åŒã³åºãããã§ããã°ãæ€çŽ¢ããããšããŠãããã®ã®åââåãšãå®éã«åºäŒã£ããã®ãå«ãŸããŠããŸãã
ãã®å ŽåããªãœãŒã¹ã®èå¥åãæž¡ããŠãã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã«ããã«ç¢ºèªããããšããå§ãããŸãã
public DrawableMatcher(final int resourceId) { super(View.class); mResDrawableId = resourceId; }
èšè¿°æ¹æ³ã¯ããã¹ãããããã°ãããšãã«éåžžã«äŸ¿å©ã§ãã ããšãã°ãç»åãäžèŽããªãå Žåãã€ã³ã¹ããŒã«ãããŠãããªãœãŒã¹ããã§ãã¯å¯Ÿè±¡ã®ãªãœãŒã¹ãšäžèŽããªãããšã瀺ãã¡ãã»ãŒãžã衚瀺ããäž¡æ¹ã®ãªãœãŒã¹ã®ããŒã¿ãæäŸããŸãã
@Override public void describeTo(Description description) { description.appendText("with drawable from resource id: "); description.appendValue(mResDrawableId); if (resourceName != null) { description.appendText("["); description.appendText(resourceName); description.appendText("]"); } }
ã«ãŒãé©åãã§ãã¯ã¡ãœããã§ã¯ãããããããã¯ã©ã¹ã®æšæºã®sameAsã¡ãœããããã§ãã¯ãããŸãã ã€ãŸããéä¿¡ãããèå¥åã«ãã£ãŠãããããããäœæãããããèæ¯ãã£ãŒã«ãã«èšå®ãããèå¥åãšæ¯èŒããŸãã
protected boolean matchesSafely(final View target) { if (!(target instanceof ImageView)) { return false; } final ImageView imageView = (ImageView) target; if (mResDrawableId < 0) { return imageView.getBackground() == null; } final Resources resources = target.getContext().getResources(); final Drawable expectedDrawable = resources.getDrawable(mResDrawableId); resourceName = resources.getResourceEntryName(mResDrawableId); if (expectedDrawable == null) { return false; } final Bitmap bitmap = getBitmap(imageView.getBackground()) final Bitmap otherBitmap = getBitmap(expectedDrawable); return bitmap.sameAs(otherBitmap); }
ããã¯ãç¹å®ã®ãªãœãŒã¹ã®ã€ã¡ãŒãžããã§ãã¯ããããšã«é¢ãããã¹ãŠã§ãã
å éšèŠçŽ ã®ã€ãã³ã
å²ãåœãŠã«åŸã£ãŠããåé€ããã¿ã³ãã¯ãªãã¯ããå¿ èŠããããããViewActionã®è¿œå ã®å®è£ ãèšè¿°ããå¿ èŠããããŸãã CustomRecyclerViewActionsãšåŒã°ããã¯ã©ã¹ã®ãŠãŒãã£ãªãã£ãäœæããŠãããšäŸ¿å©ã§ããã ViewActionå®è£ ãè¿ãå€ãã®éçã¡ãœãããå«ãŸããŠããŸãã ãã®äŸã§ã¯ãclickChildViewWithIdã䜿çšããŸãã
å§ããããã«ãViewActionã€ã³ã¿ãŒãã§ãŒã¹ãããã«è©³ããèŠãŠã¿ãŸãããã 次ã®ã¡ãœããã§æ§æãããŠããŸãã
- Matcher getConstraintsïŒïŒ-å®è¡ã®ããã®ããã€ãã®åææ¡ä»¶ã ããšãã°ãã¢ã¯ã·ã§ã³ãå®è¡ãããèŠçŽ ã衚瀺ãããå¿ èŠãããå ŽåããããŸãã
- String getDescriptionïŒïŒ-ãã¥ãŒã®ã¢ã¯ã·ã§ã³ã®èª¬æã ãã°å ã®ã¡ãã»ãŒãžã®èªã¿åãå¯èœãªè¡šç€ºãäœæããããã«å¿ èŠã§ãã
- void performïŒUiController uiControllerããã¥ãŒãã¥ãŒïŒ-å®è¡ããã¢ã¯ã·ã§ã³èªäœã
ããã§ã¯ãclickChildViewWithIdã¡ãœããã®äœæã«æ»ããŸãããã ãŠãŒã¶ãŒãã¥ãŒã®èå¥åããã©ã¡ãŒã¿ãŒã«æž¡ãããã®ãã©ã¡ãŒã¿ãŒã§ã¯ãªãã¯ã€ãã³ããå®è¡ããŸãã ãã®ã¡ãœããã®å®å šãªå®è£ ãèŠãããšãã§ããŸã
ãã¡ãã
public static ViewAction clickChildViewWithId(final int id) { return new ViewAction() { @Override public Matcher<View> getConstraints() { return null; } @Override public String getDescription() { return " id"; } @Override public void perform(final UiController uiController, final View view) { final View v = view.findViewById(id); v.performClick(); } }; }
ã¢ããã¿ãŒå ã®èŠçŽ ã®æ°ã確èªãã
ãŸããRecyclerviewå ã®èŠçŽ ã®æ°ã確èªããå¿ èŠããããŸãã ãããè¡ãã«ã¯ãç¬èªã®å®è£ ViewAssertionã€ã³ã¿ãŒãã§ã€ã¹ãå¿ èŠã§ãã ãããRecyclerViewItemCountAssertionãšåŒã³ãŸãããã
ViewAssertionã€ã³ã¿ãŒãã§ã€ã¹ã¯1ã€ã®æ¹æ³ã§ãã
void check(View view, NoMatchingViewException noViewFoundException);
ãã詳现ã«ãã§ãã¯æ¹æ³ãæ€èšããŠãã ããã ãŠãŒã¶ãŒèŠæ±ã確èªãããã®æ¹æ³ã¯ã2ã€ã®ãã©ã¡ãŒã¿ãŒãåããŸãã
-
assertThat(0,is(view.getId())
ã¹ããŒãã¡ã³ããäœæããããŠãŒã¶ãŒãã¥ãŒã®èŠçŽ ïŒããšãã°ãassertThat(0,is(view.getId())
ïŒ;èå¥åã確èªããããïŒã - NoMatchingViewException-ç¹å®ã®ããããŒããã¥ãŒã®éå±€ã®èŠçŽ ã§ã¯ãªãå Žåãã€ãŸããã¥ãŒã§ã¯ãªãå Žåã«çºçããäŸå€ã ãã®ãããªäŸå€ã«ã¯ã衚瀺ãšã³ã³ãã©ã€ã¢ã³ã¹ã«é¢ããæ å ±ãå«ãŸããŠããããããã°ã«éåžžã«äŸ¿å©ã§ãã
ãã®ã¯ã©ã¹ã®å®è£ ã¯ç°¡åã§ãã ããªããèŠãããšãã§ããå®å šãªã¯ã©ã¹ã³ãŒã
ãã¡ãã
public class RecyclerViewItemCountAssertion implements ViewAssertion { private final int mExpectedCount; public RecyclerViewItemCountAssertion(final int expectedCount) { mExpectedCount = expectedCount; } @Override public void check(final View view, final NoMatchingViewException noViewFoundException) { if (noViewFoundException != null) { throw noViewFoundException; } final RecyclerView recyclerView = (RecyclerView) view; final RecyclerView.Adapter adapter = recyclerView.getAdapter(); assertThat(adapter.getItemCount(), is(expectedCount)); } }
èŠçŽ ã®å éšè¡šçŸã確èªãã
ãªã¹ãå ã®ãã§ãã¯èŠçŽ ã®ã¯ã©ã¹ã®èª¿æ»ã«é²ã¿ãŸãã ãã®ã¯ã©ã¹ã¯ãRecyclerViewItemSpecificityViewãšåŒã°ããŸãã ã¯ã©ã¹ã³ã³ã¹ãã©ã¯ã¿ãŒã¯ãèŠçŽ èå¥åãšãããã£ãŒã®2ã€ã®ãã©ã¡ãŒã¿ãŒãåããŸãã èå¥åã䜿çšãããšããã¹ãŠãå€ããå°ãªããæ確ã«ãªããŸãããã®èŠçŽ ãåŸã§ç¢ºèªããŸãã ãããŠã2çªç®ã®ãã©ã¡ãŒã¿ãŒã¯ãæ£ç¢ºã«äœããã§ãã¯ããã®ããšãã質åã«çããŸãã åç©ã®äŸã«ç§»ããŸãããã
ãªã¹ãã®6çªç®ã®èŠçŽ ãèé£åç©ã§ãããã«ããã©ãã§ããããšã確èªããå¿ èŠããããŸãã ããã確èªããã«ã¯ãtvNameãã£ãŒã«ãã§ããã¹ããcapybaraãã«æºæ ããŠãããã©ããã確èªããå¿ èŠããããŸãã 6çªç®ã®èŠçŽ ã«æ£ç¢ºã«å°éããã«ã¯ã©ãããã°ããã§ããïŒ espresso.contribããã±ãŒãžã«ã¯ãRecyclerViewActionsã¯ã©ã¹ããããŸãã RecyclerViewã®ãã¹ãã«åœ¹ç«ã¡ããšã¹ãã¬ããœã©ã€ãã©ãªã®æšæºã¡ãœãããšå®å šã«çµåããããŸããŸãªäŸ¿å©ãªã¡ãœãããå«ãŸããŠããŸãã ããã«ãããã«ãã¬ããžã®å²åãé«ããããšãã§ããŸãã æ®å¿µãªãããRecyclerViewUtilsã¯ãã¹ãŠã®æ©èœããµããŒãããŠããŸããã ããšãã°ããªã¹ãã«ãŒãã®é ç®ãæ瀺çã«çŽæ¥ç¢ºèªããããšã¯ã§ããŸããã
RecyclerViewItemSpecificityViewã¯ã©ã¹ãæ€èšããŠãã ããã ããã¯ãViewAssertionã€ã³ã¿ãŒãã§ãŒã¹ã®å®è£ ã§ãã ãããã£ãŠãRecyclerViewItemCountAssertionãšåãããã«æ©èœããŸãããç®çã¯ç°ãªããŸãã
åè¿°ã®ããã«ãRecyclerViewItemSpecificityViewã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã¯2ã€ã®ãã©ã¡ãŒã¿ãŒãåãã次ã®æ§æãè¡šããŸãã
public RecyclerViewItemSpecificityView(final int specificallyId, final Matcher<View> matcher) { mSpecificallyId = specificallyId; mMatcher = matcher; }
ãã®å Žåã®ãã§ãã¯æ¹æ³ã¯æ¬¡ã®ããã«ãªããŸãã
- 察å¿ããèŠçŽ ãèå¥åmSpecificallyIdã§æ€çŽ¢ããŸãã ããã§ã¯ãRecyclerviewèŠçŽ ã®èå¥åãæž¡ããŸãã
- MMatcherã³ã³ãã©ã€ã¢ã³ã¹ãã§ãã¯ã ãã®ãã§ãã¯ã¯ãã¯ã©ã¹ã®ã¡ã€ã³ã¿ã¹ã¯ã§ãã
- ã³ã³ãã©ã€ã¢ã³ã¹ããªãå Žåã®å€èªå¯èœãªçµè«ã®åœ¢æã
@Override public void check(final View view, final NoMatchingViewException noViewFoundException) { final StringDescription description = new StringDescription(); description.appendText("'"); mMatcher.describeTo(description); final ViewGroup itemRoot = (ViewGroup) view; final View typeIUsage = itemRoot.findViewById(mSpecificallyId); if (noViewFoundException != null) { description.appendText( String.format( "' check could not be performed because view with id '%s' was not found.\n", mSpecificallyId)); Log.e("RecyclerViewItemSpecificityView", description.toString()); throw noViewFoundException; } else { description.appendText("' doesn't match the selected view."); assertThat(description.toString(), typeIUsage, mMatcher); } }
ããŸããçµå±ã®ãšãã-ããã¯ã«ããã©ã§ããïŒã
ãã¹ãŠã®èŠçŽ ã®æºåãã§ããŸãããã«ããã©ã確èªããŠã¿ãŸãããã ãŸããèŠçŽ ãèé£ã«ããã©ãã©ããã確èªãããã¹ããäœæããŸãã
onView(withId(R.id.rvAnimals)) .perform(scrollToPosition(capybaraPosition)) .check(new RecyclerViewItemSpecificityView(R.id.tvName, withText(""))) .check(new RecyclerViewItemSpecificityView(R.id.ivAnimalType, new DrawableMatcher(R.drawable.ic_sentiment_very_satisfied_black_24dp)));
ScrollToPositionã¯ãRecyclerViewActionsã¯ã©ã¹ã®ã¡ãœããã§ãã ãªã¹ããéžæããäœçœ®ãŸã§ã¹ã¯ããŒã«ããå¿ èŠããããŸãã ãªã¹ãé ç®ã衚瀺ãããŠããªãå Žåããã¹ãã¯å€±æããŸãã 次ã«ãtvNameãã£ãŒã«ãã®ãªã¹ããã¹ã¯ããŒã«ããèŠçŽ ã«æååãCapybaraããå«ãŸããŠããããšã確èªããŸãã ãŸãããã¹ã察象ã®ã¢ã€ãã ã¯ãèé£åç©ããªã®ã§ãã¢ã€ã³ã³ïŒivAnimalTypeïŒãic_sentiment_very_satisfied_black_24dpãšäžèŽããããšã確èªããå¿ èŠããããŸãã
次ã«ãã¢ã€ãã ãåé€ãããã¹ããäœæããŸãããã CustomRecyclerViewActionsã¯ã©ã¹ã®éçãªclickChildViewWithIdã¡ãœãããã©ã®ããã«äœ¿çšããRecyclerViewItemCountAssertionã䜿çšããŠèŠçŽ ã®æ°ãåæžãããããšã確èªããæ¹æ³ãæ¢ã«æšæž¬ããŠãããšæããŸãã
onView(withId(R.id.rvAnimals)).check(new RecyclerViewItemCountAssertion(8)); onView(withId(R.id.rvAnimals)).perform( RecyclerViewActions.actionOnItemAtPosition(0, MyRecyclerViewActions.clickChildViewWithId(R.id.btnRemove))); onView(withId(R.id.rvAnimals)).check(new RecyclerViewItemCountAssertion(7));
å ·äœçã«ã¯ãRecyclerViewActionsã¯ã©ã¹ã®actionOnItemAtPositionã¡ãœããã䜿çšããŸããã 圌ã¯ãªã¹ããçŸåšã®äœçœ®ãŸã§ã¹ã¯ããŒã«ããããã«ãã£ãŠãªã¹ãé ç®ãæäœããæ©äŒãäžããŠãããŸãã
ãããã«
ãã¹ãã¯éåžžã«éèŠãªããã»ã¹ã§ãã
ãã¿ã¹ã¯ã¯ãå°ãªããšã70ïŒ ã®ãã¹ãã§ã«ããŒããããŸã§å®äºã§ããŸãããã-ããã§ãããŒãã¯ãç§ãããã°ã©ãã³ã°ã®çŽ æŽãããäžçãç¥ãå§ãããšãã«ç§ã«èšã£ãã ç§ã®æèŠã§ã¯ãéèŠãªãã¹ãåºæºã¯ã«ãã¬ããžãšãªã¢ã§ãã ãŸã第äžã«-ãœãããŠã§ã¢è£œåã®ãã¹ããããéšåã®åºæ¬æ©èœããã§ãã¯ãã次ã«-ããã€ãã®ç·æ¥äºæ ã§ã¢ããªã±ãŒã·ã§ã³ãå°å ¥ããããšããŸãã ããã«ããããœãããŠã§ã¢è£œåã®å質ãå®å®æ§ãåäžããæãéèŠãªã®ã¯ç解ã§ãã ã¯ãããããŠäœãšé ãã¹ã眪ã§ããããã°ã©ã ããã¹ãã§èŠãããŠãããšãããã°ã©ã ã¯ãã£ãšéãã«ç ããŸãã
ä»æ¥ã¯ãããã¹ã¿ãŒã¹ãŒãã±ãŒã¹ãã®äœæã«åãçµãæ¹æ³ã«ã€ããŠè©±ããŸãããããããè£å®ããã®ã«åœ¹ç«ã€äœããèŠã€ããŠããã ããã°å¹žãã§ãã ããã§å®å šãªäŸãèŠã€ããããšãã§ããŸãã