
èšäºã®ç¬¬1éšãšç¬¬2éšã§ã¯ã Githubãšé£æºããããã®ã¢ããªã±ãŒã·ã§ã³ãäœæããDagger 2ãå®è£ ãããŠãããã³ãŒãããã¹ãã§ã«ããŒããŸããã æåŸã®éšåã§ã¯ãçµ±åãã¹ããšæ©èœãã¹ããäœæããTDDææ³ãæ€èšãããã®äœ¿çšã«åãããŠæ°ããæ©èœãäœæãã次ã«èªãã¹ãããšã説æããŸãã
å®å
šãªã³ã³ãã³ã
- ã¯ããã«
- ã¹ããã1.ã·ã³ãã«ãªã¢ãŒããã¯ãã£
- ã¹ããã2.è€éãªã¢ãŒããã¯ãã£
- ã¹ããã3.äŸåæ§æ³šå ¥
- ã¹ããã4.ãŠããããã¹ã
- ã¹ããã5.çµ±åãã¹ã
- ã¹ããã6.æ©èœãã¹ã
- ã¹ããã7. TDD
- ã¹ããã8.次ã¯äœã§ããïŒ
- ãããã«
ã¯ããã«
èšäºã®æåã®éšåã§ã¯ãgithubã2段éã§æäœããããã®ç°¡åãªã¢ããªã±ãŒã·ã§ã³ãäœæããŸããã ã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ã¯ãåçŽãªéšåãšè€éãªéšåã®2ã€ã®éšåã«åããããŸããã 2çªç®ã®éšåã§ã¯ãDagger 2ãå®è£ ããRobolectricãMockitoãMockWebServerãããã³JaCoCoã䜿çšãããã¹ãã§ãŠãããã³ãŒããã«ããŒããŸããã
æ¡ä»¶ä»ãé©çšã¹ããŒã

ãã¹ãŠã®ãœãŒã¹ã³ãŒãã¯Githubã«ãããŸãã
ã¹ããã5.çµ±åãã¹ã
çµ±åãã¹ãã¯ãåã ã®ãœãããŠã§ã¢ã¢ãžã¥ãŒã«ãã°ã«ãŒãã§çµã¿åãããŠãã¹ããããœãããŠã§ã¢ãã¹ãã®ãã§ãŒãºã®1ã€ã§ãã
çµ±åãã¹ãã«ã¯3ã€ã®ã¢ãããŒãããããŸãã
ããã ã¢ããïŒããã ã¢ããçµ±åïŒ
äœã¬ãã«ã®ã¢ãžã¥ãŒã«ãããã·ãŒãžã£ããŸãã¯é¢æ°ã¯ãã¹ãŠãŸãšãããããã¹ããããŸãã ãã®åŸã次ã®ã¬ãã«ã®ã¢ãžã¥ãŒã«ã§çµ±åãã¹ããå®æœããŸãã ãã®ã¢ãããŒãã¯ãéçºã¬ãã«ã®ãã¹ãŠãŸãã¯ã»ãšãã©ãã¹ãŠã®ã¢ãžã¥ãŒã«ã®æºåãæŽã£ãŠããå Žåã«æçšãšèããããŸãã ãŸãããã®ã¢ãããŒãã¯ããã¹ãçµæããã¢ããªã±ãŒã·ã§ã³ã®æºåç¶æ³ã®ã¬ãã«ãå€æããã®ã«åœ¹ç«ã¡ãŸãã
ãããããŠã³çµ±å
æåã«ããã¹ãŠã®é«ã¬ãã«ã¢ãžã¥ãŒã«ããã¹ããããåŸã ã«äœã¬ãã«ã¢ãžã¥ãŒã«ãè¿œå ãããŸãã äžäœã¬ãã«ã®ãã¹ãŠã®ã¢ãžã¥ãŒã«ã¯ãåæ§ã®æ©èœãåãããã©ã°ã«ãã£ãŠã·ãã¥ã¬ãŒããããæºåãã§ããããå®éã®ã¢ã¯ãã£ããªã³ã³ããŒãã³ãã«çœ®ãæããããŸãã ãããã£ãŠãäžããäžãžãã¹ããå®æœããŸãã
ããã°ãã³ïŒãããã°ãã³ãçµ±åïŒ
éçºããããã¹ãŠãŸãã¯ã»ãšãã©ãã¹ãŠã®ã¢ãžã¥ãŒã«ã¯ãå®å šãªã·ã¹ãã ãŸãã¯ãã®äž»èŠéšåãšããŠçµã¿ç«ãŠãããçµ±åãã¹ããå®è¡ãããŸãã ãã®ã¢ãããŒãã¯ãæéãç¯çŽããã®ã«éåžžã«é©ããŠããŸãã
ãã¹ãŠã®ã¢ãžã¥ãŒã«ãçšæãããŠããã®ã§ã ããã ã¢ããã¢ãããŒãã䜿çšããŸãã
å埩ã¢ãããŒã
å埩ã¢ãããŒãã䜿çšããŸããã€ãŸããã¢ãžã¥ãŒã«ã1ã€ãã€ãããã ã¢ããã§æ¥ç¶ããŸãã æåã«ãAPI +ã¢ãã«ã®æã次ã«API +ã¢ãã«+ããããŒ+ãã¬ãŒã³ã¿ãŒã次ã«äžè¬çãªAPI +ã¢ãã«ããããŒ+ãã¬ãŒã³ã¿ãŒ+ãã¥ãŒã確èªããŸãã
åŠå®çããã³è¯å®çãªã·ããªãª
çµ±åãã¹ãã§ã¯ããµãŒããŒããã®2ã€ã®å¿çã·ããªãªãæ€èšããå¿ èŠããããŸããéåžžã®å¿çãšãšã©ãŒã§ãã ããã«å¿ããŠãã³ã³ããŒãã³ãã®åäœãå€ãããŸãã åãã¹ãã®åã«ããµãŒããŒïŒMockWebServerïŒããã®å¿çãæ§æããçµæã確èªã§ããŸãã
çµ±åãã¹ãã¹ããŒã ïŒAPI +ã¢ãã«ïŒïŒ

çµ±åãã¹ãã®äŸïŒAPI +ã¢ãã«ïŒãRetrfofitã¢ãžã¥ãŒã«ãšModelImplã®çžäºäœçšã確èªããŸãã
çµ±åãã¹ãã®äŸ
@Test public void testGetRepoList() { TestSubscriber<List<RepositoryDTO>> testSubscriber = new TestSubscriber<>(); model.getRepoList(TestConst.TEST_OWNER).subscribe(testSubscriber); testSubscriber.assertNoErrors(); testSubscriber.assertValueCount(1); List<RepositoryDTO> actual = testSubscriber.getOnNextEvents().get(0); assertEquals(7, actual.size()); assertEquals("Android-Rate", actual.get(0).getName()); assertEquals("andrey7mel/Android-Rate", actual.get(0).getFullName()); assertEquals(26314692, actual.get(0).getId()); }
çµ±åãã¹ãã¹ããŒã ïŒAPI +ã¢ãã«+ããããŒ+ãã¬ãŒã³ã¿ãŒïŒïŒ

çµ±åãã¹ãã®äŸïŒAPI +ã¢ãã«+ããããŒ+ãã¬ãŒã³ã¿ãŒïŒ
@Test public void testLoadData() { repoInfoPresenter.onCreateView(null); repoInfoPresenter.onStop(); verify(mockView).showBranches(branchList); verify(mockView).showContributors(contributorList); } @Test public void testLoadDataWithError() { setErrorAnswerWebServer(); repoInfoPresenter.onCreateView(null); repoInfoPresenter.onStop(); verify(mockView, times(2)).showError(TestConst.ERROR_RESPONSE_500); }
ãã®çµæããã¹ãŠã®ã¢ãžã¥ãŒã«ã®çžäºäœçšã®å®å šãªãã§ãã¯ãããã ã¢ããã§ååŸãããŸãã ã¢ãžã¥ãŒã«ãã©ããã§æ£ããçžäºäœçšããªãå Žåããã¹ããéããŠãããããã«ç¢ºèªã§ããŸãã
ã¹ããã6.æ©èœãã¹ã
æ©èœãã¹ãã¯ãæ©èœèŠä»¶ã®å®çŸå¯èœæ§ãã€ãŸãç¹å®ã®æ¡ä»¶äžã§ãŠãŒã¶ãŒãå¿ èŠãšããã¿ã¹ã¯ã解決ãããœãããŠã§ã¢ã®èœåãæ€èšŒããããã®ãœãããŠã§ã¢ãã¹ãã§ãã æ©èœèŠä»¶ã«ããããœãããŠã§ã¢ã®æ©èœã解決ããã¿ã¹ã¯ã決ãŸããŸãã
Androidã¢ããªã±ãŒã·ã§ã³ã®äžéšãšããŠããŠãŒã¶ãŒã®èŠç¹ããã¢ããªã±ãŒã·ã§ã³ããã¹ãããŸãã ãŸããã«ã¹ã¿ã ã¢ããªã±ãŒã·ã§ã³ããããäœæããŸãã
ã¢ããªã±ãŒã·ã§ã³ããã

å¿ èŠãªãã¹ãã±ãŒã¹ãäœæããŸãã
- ã¢ããªã±ãŒã·ã§ã³ãéãããã¹ãŠã®èŠçŽ ã®å¯èŠæ§ã確èªããŸã
- ãã¹ããŠãŒã¶ãŒãå
¥åãã[æ€çŽ¢]ãã¯ãªãã¯ããŸã
- åä¿¡ããããŒã¿-ãªããžããªã®ãªã¹ããååŸããããŒã¿è¡šç€ºã確èªããŸãã
- ããŒã¿ãåä¿¡ããŠââããŸãã-ãšã©ãŒè¡šç€ºã確èªããŠãã ããã
- 2çªç®ã®ç»é¢ã«ç§»åãããŠãŒã¶ãŒåãšãªããžããªåãæ£ãã衚瀺ãããŠãããã©ããã確èªããŸãã
- ãã©ã³ããšè²¢ç®è ã®ãªã¹ããååŸããããŒã¿è¡šç€ºã確èª
- åä¿¡ãããªãã£ããªã¹ãïŒ2ã€ã®ãã¹ãïŒãåä¿¡ããããªã¹ãã®è¡šç€ºããšã©ãŒè¡šç€ºã確èªããŸã
- äž¡æ¹ã®ãªã¹ããåä¿¡ãããªãããšã©ãŒè¡šç€ºã確èªãã
ãã¹ãã«ã¯ããšã¹ãã¬ããœã䜿çšããŸãã ä»ã®ãã¹ããšåæ§ã«ãã¢ãã¯ãšäºåã«æºåãããjsonãã¡ã€ã«ã䜿çšããŠãã€ã³ã¿ãŒãããããã¢ããªã±ãŒã·ã§ã³ãåé¢ããŸãã Dagger 2ã¯ããããšã³ã³ããŒãã³ãã®çœ®æã«åœ¹ç«ã¡ãŸãã
MockTestRunnerãšTestAppã³ãŒã
public class MockTestRunner extends AndroidJUnitRunner { @Override public Application newApplication( ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication( cl, TestApp.class.getName(), context); } } public class TestApp extends App { @Override protected TestComponent buildComponent() { return DaggerTestComponent.builder().build(); } }
ãšã¹ãã¬ããœãã¹ãã®äŸ
@Test public void testGetUserRepo() { apiConfig.setCorrectAnswer(); onView(withId(R.id.edit_text)).perform(clearText()); onView(withId(R.id.edit_text)).perform(typeText(TestConst.TEST_OWNER)); onView(withId(R.id.button_search)).perform(click()); onView(withId(R.id.recycler_view)).check(EspressoTools.hasItemsCount(7)); onView(withId(R.id.recycler_view)).check(EspressoTools.hasViewWithTextAtPosition(0, "Android-Rate")); onView(withId(R.id.recycler_view)).check(EspressoTools.hasViewWithTextAtPosition(1, "android-simple-architecture")); onView(withId(R.id.recycler_view)).check(EspressoTools.hasViewWithTextAtPosition(2, TestConst.TEST_REPO)); } @Test public void testGetUserRepoError() { apiConfig.setErrorAnswer(); onView(withId(R.id.edit_text)).perform(clearText()); onView(withId(R.id.edit_text)).perform(typeText(TestConst.TEST_OWNER)); onView(withId(R.id.button_search)).perform(click()); onView(allOf(withId(android.support.design.R.id.snackbar_text), withText(TestConst.TEST_ERROR))) .check(matches(isDisplayed())); onView(withId(R.id.recycler_view)).check(EspressoTools.hasItemsCount(0)); }
åæ§ã«ããã¹ãã±ãŒã¹ã®æ®ãã®ãã¹ããèšè¿°ããŸãã
Espressoã§ã®äœæ¥ãçµäºããããã¢ãžã¥ãŒã«åŒã®çµ±åãã¹ããšæ©èœãã¹ãã§ã¢ããªã±ãŒã·ã§ã³ãå®å šã«ã«ããŒããŸãã
ã¹ããã7. TDD

ãã¹ãé§åéçºãšã¯ããã¹ãã®äœæãéããŠéçºãå®çŸ©ãããœãããŠã§ã¢éçºææ³ã§ãã
åºæ¬çã«ã次ã®3ã€ã®ç°¡åãªç¹°ãè¿ãæé ãå®è¡ããå¿ èŠããããŸãã
-è¿œå ããå¿ èŠãããæ°ããæ©èœã®ãã¹ããäœæããŸãã
-ãã¹ãã«åæ Œããã³ãŒããèšè¿°ããŸãã
-æ°ããã³ãŒããšå€ãã³ãŒãããªãã¡ã¯ã¿ãªã³ã°ããŸãã
ç¥èªTDDã«ãªãã¿ããªãå Žåã¯ãiOSéšéã®ååããã® èšäºãŸãã¯TDDããããã® èšäºãèªãããšããå§ãããŸãã
TDDã«ã¯3ã€ã®æ³åããããŸãã
- æ¬çªã³ãŒãã¯ãã¢ã€ãã«ãã¹ããè¡ââãããåã«äœæãããŸããã
- ãšã©ãŒã«ååãªæ°ã®åäœãã¹ãã³ãŒãã¯æžã蟌ãŸããŸããã
- çŸåšã®ã¢ã€ãã«ãã¹ãã«åæ Œããã®ã«ååãªéã®ãããã¯ã·ã§ã³ã³ãŒãã¯æžã蟌ãŸããŸããã
ããšãã°ãã€ã³ã¿ãŒãããããã®ããŠã³ããŒãã衚瀺ããããã°ã¬ã¹ããŒãäœæããŸãã ããŒã¿ãããŠã³ããŒãããããšè¡šç€ºãããããŒã¿ãããŒããããããšã©ãŒãçºçãããšè¡šç€ºãããªããªããŸãã ãã¹ãŠã®éçºã¯TDDã§è¡ãããŸãã
ãã®æ©èœã®éçºã¯ããã¬ãŒã³ã¿ãŒãšãã©ã°ã¡ã³ãã«åœ±é¿ããããããŒãšæ¥ä»ã¬ã€ã€ãŒã¯å€æŽãããŸããã
çºè¡šè
ãªããžããªã®ãªã¹ãããå§ããŸãããã ãŸããã€ã³ã¿ãŒãã§ãŒã¹ãè¿œå ããŸãããã
public interface RepoListView extends View { void showRepoList(List<Repository> list); void showEmptyList(); String getUserName(); void startRepoInfoFragment(Repository repository); //New void showLoading(); void hideLoading(); }
æåã®æ®µéã
æåã«ãéåžžã®ããŒãã®å Žåããã©ã°ã¡ã³ãã§showLoadingã¡ãœãããåŒã³åºãããããšã確èªãããã¹ããäœæããŸãã
@Test public void testShowLoading() { repoListPresenter.onSearchButtonClick(); verify(mockView).showLoading(); }
å£ãããã¹ããååŸãããããã«ãåæ Œããã³ãŒããèšè¿°ããŸãã
public void onSearchButtonClick() { String name = view.getUserName(); if (TextUtils.isEmpty(name)) return; view.showLoading(); // --- some code --- }
ãªãã¡ã¯ã¿ãªã³ã°ãããã®ã¯ãŸã ãããŸããã
ãããæåã®TDDéçºã®å埩ãçµäºããå Žæã§ãã æ°ããæ©èœãšãã®ãã¹ããååŸããŸããã
第äºæ®µéã
éåžžã®ããŒãåŸã«ããã©ã°ã¡ã³ãã«å¯ŸããŠhideLoadingã¡ãœãããåŒã³åºãããããšã確èªãããã¹ããäœæããŸãã
@Test public void testHideLoading() { repoListPresenter.onSearchButtonClick(); verify(mockView).hideLoading(); }
ãã¹ãã«åæ Œããã³ãŒããèšè¿°ããŸãã
//-- view.showLoading(); Subscription subscription = model.getRepoList(name) .map(repoListMapper) .subscribe(new Observer<List<Repository>>() { @Override public void onCompleted() { view.hideLoading(); } @Override public void onError(Throwable e) { view.showError(e.getMessage()); } @Override public void onNext(List<Repository> list) { if (list != null && !list.isEmpty()) { repoList = list; view.showRepoList(list); } else { view.showEmptyList(); } } });
ãªãã¡ã¯ã¿ãªã³ã°ã¯å¿ èŠãããŸããã
3çªç®ãš4çªç®ã®ã¹ããŒãžã
次ã«ããšã©ãŒãçºçãããšãã«å¿ èŠãªã¡ãœãããæ£ããåŒã³åºãããããšã確èªãããã¹ããäœæããŸãã
ãšã©ãŒåŠçãã¹ã
@Test public void testShowLoadingOnError() { doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoList(TestConst.TEST_OWNER); repoListPresenter.onSearchButtonClick(); verify(mockView).showLoading(); } @Test public void testHideLoadingOnError() { doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoList(TestConst.TEST_OWNER); repoListPresenter.onSearchButtonClick(); verify(mockView).hideLoading(); }
ãšã©ãŒåŠçã³ãŒã
//-- @Override public void onError(Throwable e) { view.showError(e.getMessage()); view.hideLoading(); } //--
ãªãã¡ã¯ã¿ãªã³ã°ã¯å¿ èŠãããŸããã ã¬ããªã¹ããã¬ãŒã³ã¿ãŒãšã®äœæ¥ã¯å®äºããŸãããã¬ãæ å ±ãã¬ãŒã³ã¿ãŒã«ç§»ããŸãããã
ãªããžããªæ å ±ãã¬ãŒã³ã¿ãŒ
åã®ã¹ããããšåæ§ã«ãæ£ããããŒã¿ãããŒãããããã®ãã¹ããšã³ãŒããäœæããŸãã
æ£ããããŒã¿èªã¿èŸŒã¿ã®ãã¹ã
@Test public void testShowLoading() { repoInfoPresenter.onCreateView(null); verify(mockView).showLoading(); } @Test public void testHideLoading() { repoInfoPresenter.onCreateView(null); verify(mockView).hideLoading(); }
ããŒã¿ãæ£ããããŒãããããã®ã³ãŒã
public void loadData() { String owner = repository.getOwnerName(); String name = repository.getRepoName(); view.showLoading(); Subscription subscriptionBranches = model.getRepoBranches(owner, name) .map(branchesMapper) .subscribe(new Observer<List<Branch>>() { @Override public void onCompleted() { hideInfoLoadingState(); } @Override public void onError(Throwable e) { view.showError(e.getMessage()); } @Override public void onNext(List<Branch> list) { branchList = list; view.showBranches(list); } }); addSubscription(subscriptionBranches); Subscription subscriptionContributors = model.getRepoContributors(owner, name) .map(contributorsMapper) .subscribe(new Observer<List<Contributor>>() { @Override public void onCompleted() { hideInfoLoadingState(); } @Override public void onError(Throwable e) { view.showError(e.getMessage()); } @Override public void onNext(List<Contributor> list) { contributorList = list; view.showContributors(list); } }); addSubscription(subscriptionContributors); } protected void hideInfoLoadingState() { countCompletedSubscription++; if (countCompletedSubscription == COUNT_SUBSCRIPTION) { view.hideLoading(); countCompletedSubscription = 0; } }
ãªãã¡ã¯ã¿ãªã³ã°
ã芧ã®ãšããã2人ã®ãã¬ãŒã³ã¿ãŒã«åãã³ãŒãã䜿çšãããŠããŸãïŒããŠã³ããŒãã€ã³ãžã±ãŒã¿ãŒã®è¡šç€ºãšé衚瀺ããšã©ãŒã®è¡šç€ºïŒã å ±éã®åºæ¬ã¯ã©ã¹BasePresenterã«é 眮ããå¿ èŠããããŸãã BasePresenterã§showLoadingStateïŒïŒhideLoadingStateïŒïŒããã³showErrorïŒThrowable eïŒã¡ãœãããåŒãåºããŸã
BasePresenterã³ãŒã
protected abstract View getView(); protected void showLoadingState() { getView().showLoadingState(); } protected void hideLoadingState() { getView().hideLoadingState(); } protected void showError(Throwable e) { getView().showError(e.getMessage()); }
RepoInfoPresenterããªãã¡ã¯ã¿ãªã³ã°ãããã¹ãŠã®ãã¹ãã«åæ Œããããšã確èªããŸãã RepoListPresenterããªãã¡ã¯ã¿ãªã³ã°ããŠåºæ¬ã¯ã©ã¹ãæäœããããšãå¿ããªãã§ãã ããã
次ã«ãæåã«ãã¹ããèšè¿°ãã次ã«ïŒRepoInfoPresenterã®ïŒããŒãäžã«ãšã©ãŒãåŠçããããã®ã³ãŒããèšè¿°ããŸãã
èµ·åäžã®ãšã©ãŒåŠçã®ãã¹ã
@Test public void testShowLoadingOnError() { doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoContributors(TestConst.TEST_OWNER, TestConst.TEST_REPO); doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoBranches(TestConst.TEST_OWNER, TestConst.TEST_REPO); repoInfoPresenter.onCreateView(null); verify(mockView).showLoading(); } @Test public void testHideLoadingOnError() { doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoContributors(TestConst.TEST_OWNER, TestConst.TEST_REPO); doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoBranches(TestConst.TEST_OWNER, TestConst.TEST_REPO); repoInfoPresenter.onCreateView(null); verify(mockView).hideLoading(); } @Test public void testShowLoadingOnErrorBranches() { doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoBranches(TestConst.TEST_OWNER, TestConst.TEST_REPO); repoInfoPresenter.onCreateView(null); verify(mockView).showLoading(); } @Test public void testHideLoadingOnErrorBranches() { doAnswer(invocation -> Observable.error(new Throwable(TestConst.TEST_ERROR))) .when(model) .getRepoBranches(TestConst.TEST_OWNER, TestConst.TEST_REPO); repoInfoPresenter.onCreateView(null); verify(mockView).hideLoading(); }
èµ·åæã«ãšã©ãŒãåŠçããããã®ã³ãŒã
showLoadingState(); Subscription subscriptionBranches = model.getRepoBranches(owner, name) .map(branchesMapper) .subscribe(new Observer<List<Branch>>() { @Override public void onCompleted() { hideInfoLoadingState(); } @Override public void onError(Throwable e) { hideInfoLoadingState(); showError(e); } @Override public void onNext(List<Branch> list) { branchList = list; view.showBranches(list); } }); addSubscription(subscriptionBranches); Subscription subscriptionContributors = model.getRepoContributors(owner, name) .map(contributorsMapper) .subscribe(new Observer<List<Contributor>>() { @Override public void onCompleted() { hideInfoLoadingState(); } @Override public void onError(Throwable e) { hideInfoLoadingState(); showError(e); } @Override public void onNext(List<Contributor> list) { contributorList = list; view.showContributors(list); } });
ãã¬ãŒã³ã¿ãŒã®éçºã¯çµäºããŸããã ãã©ã°ã¡ã³ãã«æž¡ããŸãã
ãã©ã°ã¡ã³ã
å ±éã®èŠçŽ ãšããŠã®ããã°ã¬ã¹ããŒã¯ã¢ã¯ãã£ããã£å ã«ããããã©ã°ã¡ã³ãã¯ã¢ã¯ãã£ããã£ã®showProgressBarïŒïŒããã³hideProgressBarïŒïŒã¡ãœãããåŒã³åºããããã°ã¬ã¹ããŒã衚瀺ãŸãã¯é衚瀺ã«ããŸãã ã¢ã¯ãã£ããã£ãæäœããã«ã¯ãActivityCallbackã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããŸãã ãã¬ãŒã³ã¿ãŒã®çµéšãããå ±éã®åºæ¬ã¯ã©ã¹ã§ããBaseFragmentãå¿ èŠã§ãããšããã«æšæž¬ã§ããŸãã ã¢ã¯ãã£ããã£ãšã®çžäºäœçšã®ããžãã¯ãå«ãŸããŸãã
æåã«ãåºæ¬ãã©ã°ã¡ã³ããšã¢ã¯ãã£ããã£ã®çžäºäœçšã«é¢ãããã¹ããäœæãã次ã«ã³ãŒããäœæããŸãã
åºæ¬ãã©ã°ã¡ã³ããã¹ã
@Test public void testAttachActivityCallback() throws Exception { assertNotNull(baseFragment.activityCallback); } @Test public void testShowLoadingState() throws Exception { baseFragment.showLoading(); verify(activity).showProgressBar(); } @Test public void testHideLoadingState() throws Exception { baseFragment.hideLoading(); verify(activity).hideProgressBar(); }
åºæ¬ãã©ã°ã¡ã³ãã³ãŒã
@Override public void onAttach(Activity activity) { super.onAttach(activity); try { activityCallback = (ActivityCallback) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement activityCallback"); } } @Override public void showLoading() { activityCallback.showProgressBar(); } @Override public void hideLoading() { activityCallback.hideProgressBar(); }
ãªãã¡ã¯ã¿ãªã³ã°ã¯äžèŠã§ããã¢ã¯ãã£ããã£ã«é²ãã§ãã ããã
掻åæ§
æåŸã®ã¹ãããã¯ãã¢ã¯ãã£ããã£ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããããšã§ãã ã³ãã³ãã«å¿ããŠãprogressBarã®setVisibilityãå€æŽããŸãã ãã¹ãã§ã¯ãprogressBarãæ€åºãããshowProgressBarã¡ãœãããšhideProgressBarã¡ãœãããæ©èœããããšã確èªããå¿ èŠããããŸãã
æåã«ãã¹ããäœæããŸãã
ã¢ã¯ãã£ããã£ãã¹ã
@Test public void testHaveProgressBar() throws Exception { assertNotNull(progressBar); } @Test public void testShowProgressBar() throws Exception { mainActivity.showProgressBar(); verify(progressBar).setVisibility(View.VISIBLE); } @Test public void testHideProgressBar() throws Exception { mainActivity.hideProgressBar(); verify(progressBar).setVisibility(View.INVISIBLE); }
次ã«ãã³ãŒããèšè¿°ããŸãã
ã¢ã¯ãã£ããã£ã³ãŒã
@Bind(R.id.toolbar_progress_bar) protected ProgressBar progressBar; //---- some code ---- @Override public void showProgressBar() { progressBar.setVisibility(View.VISIBLE); } @Override public void hideProgressBar() { progressBar.setVisibility(View.INVISIBLE); }
ãã¹ãŠãéåžžã«ç°¡åã§ããªãã¡ã¯ã¿ãªã³ã°ã¯å¿ èŠãããŸããã
ããã§ãTDDææ³ã䜿çšããããã°ã¬ã¹ããŒã®éçºãçµäºããŸãã
ã¹ããã8.次ã¯äœã§ããïŒ
TDDãç 究ããããŒããããã³ã°ãéçºããããã¢ããªã±ãŒã·ã§ã³ã®éçºãçµäºããŸãã ãããªãéçºã®ããã«ã次ã®èšäºãèªãããšããå§ãããŸãã
Android Clean Architecture
Android Clean Architectureã¯Fernando Cejasã®æåãªèšäºã§ãUncle Bobã®Clean Architectureã«åºã¥ããŠããŸãã ãã¬ãŒã³ããŒã·ã§ã³å±€ããã¡ã€ã³å±€ãããŒã¿å±€ã®3ã€ã®å±€ã®éã®çžäºäœçšãèæ ®ãããŸãã habrahabrãžã®ç¿»èš³ããããŸãã
VIPER
VIPERïŒViewãInteractorãPresenterãEntityãRoutingïŒã®äººæ°ãé«ãŸã£ãŠããŸããVikkoSã®èšäºVIPER Jet-powered VIPERã§è©³ããç¥ãããšãã§ããŸãã VIPERã®äž»èŠãªååã¯ãiOSã®ååããã®èšäºãšã¬ããŒãã§åãäžããããŠããŸãã
ã¢ã¹ããŒ
Mosbyã¯ãMVPã¢ããªã±ãŒã·ã§ã³ãäœæããããã®äžè¬çãªã©ã€ãã©ãªã§ãã ãã¹ãŠã®ã¡ã€ã³ã€ã³ã¿ãŒãã§ã€ã¹ãšåºæ¬ã¯ã©ã¹ãå«ãŸããŠããŸãã ãŠã§ããµã€ãïŒ http : //hannesdorfmann.com/mosby/ GithubïŒ https : //github.com/sockeqwe/mosby
Androidã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£
RibotããŒã ã®ã¢ãŒããã¯ãã£ã«é¢ããè¯ãèšäº-Androidã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ã㣠ã AsyncTaskããRxJavaãžã®ç§»è¡ãèæ ®ãããŸãã æè¿ã habrahabrãžã®ç¿»èš³ããªãªãŒã¹ãããŸããã
Androidéçºæåææž
Androidéçºã«ã«ãã£ãŒããã¥ã¡ã³ãArtem_zinã«ãã #qualitymatters Artem Zinnatullinã«ããçŽ æŽãããèšäºãšãã¢ãããžã§ã¯ãã ãã®èšäºã§ã¯ãAndroidã¢ããªã±ãŒã·ã§ã³ãéçºããããã®8ã€ã®ååã«ã€ããŠèª¬æããŠããŸããGithubã®äŸã§ãµããŒããããŠããŸãã
ãããã«
ãã®äžé£ã®èšäºã§ã¯ãã¢ããªã±ãŒã·ã§ã³éçºã®ãã¹ãŠã®æ®µéã«ã€ããŠèª¬æããŸããã ã·ã³ãã«ãªMVPããŒã¹ã®ã¢ãŒããã¯ãã£ããå§ãããããæ°ããæ©èœãè¿œå ããã®ãé£ãããªããŸããã ãªã¢ã¯ãã£ãããã°ã©ãã³ã°ãšã³ãŒã«ããã¯ãåãé€ãããã®RxJavaãšRxAndroidããããã¯ãŒã¯ãšã®äŸ¿å©ãªäœæ¥ã®ããã®Retrofitãè¿ éã§ç°¡åãªãã¥ãŒæ€çŽ¢ã®ããã®Butterknifeãªã©ã®ææ°ã®ã©ã€ãã©ãªã䜿çšããŸããã Dagger 2ã¯ãã¹ãŠã®äŸåé¢ä¿ã管çãããã¹ãã®äœææã«éåžžã«è²ŽéãªãµããŒããæäŸããŸããã jUnitãRobolectricãMockitoãMockWebServerã䜿çšããŠããã¹ãèªäœãäœæããŸããã ãããŠããšã¹ãã¬ããœã¯ããã¹ã¿ãŒãååž°ãã¹ãã®èŠåŽããæããŸããã
ãããžã§ã¯ãããã¹ãã§å®å šã«ã«ããŒããŸããã åäœãã¹ãã§ã¯åã³ã³ããŒãã³ããåå¥ã«ãã¹ãããçµ±åãã¹ãã§ã¯å šäœçãªçžäºäœçšããã¹ãããæ©èœãã¹ãã§ã¯ãã¹ãŠããŠãŒã¶ãŒåŽãã調ã¹ãŸãã ããã°ã©ã ãããã«å€æŽããŠããäžéšã®ã³ã³ããŒãã³ããç Žæããããšãæããããšã¯ã§ããŸããïŒãŸãããŸãã¯ã»ãšãã©æããŸããïŒãäœããèœã¡ãŠããªãªãŒã¹ã«ãã°ãå ¥ã蟌ã¿ãŸãã TDDã®ãããã§ãã»ãšãã©ã®ã³ãŒãã¯ãã¹ãã§ã«ããŒãããŸãïŒãã¹ããªããã³ãŒããªãïŒã éšåçãªã«ãã¬ããžããã³ãŒãã¯äœæãããŸãããããã¹ãã®æéã¯ãããŸããã§ããããšããåé¡ã¯ãããŸããã
ãã¹ãŠã®ãããžã§ã¯ãã³ãŒãã¯GithubïŒhttps://github.com/andrey7mel/android-step-by-stepïŒã§å ¥æã§ããŸã
ãã®ã·ãªãŒãºã®èšäºãã楜ãã¿ããã ããã圹ã«ç«ãŠã°å¹žãã§ãããæž èŽããããšãããããŸããã