èšäºã®æåã®éšåã§ã¯ ãMVPãã¿ãŒã³ã䜿çšããŠã¬ã€ã€ãŒã§åºåããã2ã€ã®ç»é¢ã§æ§æãããgithubãæäœããããã®ã¢ããªã±ãŒã·ã§ã³ãéçºããŸããã RxJavaã䜿çšããŠããµãŒããŒãšç°ãªãã¬ã€ã€ãŒã®2ã€ã®ããŒã¿ã¢ãã«ãšã®å¯Ÿè©±ãç°¡çŽ åããŸããã 第2éšã§ã¯ãDagger 2ã玹ä»ãããŠããããã¹ããèšè¿°ããMockWebServerãJaCoCoãããã³RobolectricãèŠãŠãããŸãã
å
容ïŒ
ã¯ããã«
èšäºã®æåã®éšåã§ã¯ãgithubã2段éã§æäœããããã®ç°¡åãªã¢ããªã±ãŒã·ã§ã³ãäœæããŸããã
æ¡ä»¶ä»ãé©çšã¹ããŒã ãã¹ãŠã®ãœãŒã¹ã³ãŒãã¯
Githubã«ãããŸãã ãªããžããªå
ã®ãã©ã³ãã¯ãèšäºã®ã¹ãããã«å¯Ÿå¿ããŠããŸãã
ã¹ããã3äŸåæ§æ³šå
¥-3çªç®ã®ã¹ãããã
ã¹ããã4ãŠããããã¹ã-4çªç®ã®ã¹ãããã
ã¹ããã3.äŸåæ§æ³šå
¥
Dagger 2ã䜿çšããåã«ã
Dependency InjectionïŒDependency InjectionïŒã®åçãç解ããå¿
èŠããããŸãã
ãªããžã§ã¯ãBãå«ããªããžã§ã¯ãAããããšããŸããDIã䜿çšããªãå Žåãã¯ã©ã¹Aã®ã³ãŒãã§ãªããžã§ã¯ãBãäœæããå¿
èŠããããŸããããšãã°ã次ã®ããã«ãªããŸãã
public class A { B b; public A() { b = new B(); } }
ãã®ãããªã³ãŒãã¯ã
SOLIDååã®
SRPããã³
DRPã«çŽã¡ã«éåããŸãã æãç°¡åãªè§£æ±ºçã¯ããªããžã§ã¯ãBãã¯ã©ã¹Aã®ã³ã³ã¹ãã©ã¯ã¿ãŒã«æž¡ãããšã§ããããã«ããããæåã§ãäŸåæ§æ³šå
¥ãå®è£
ããŸãã
public class A { B b; public A(B b) { this.b = b; } }
éåžžãDIã¯ãµãŒãããŒãã£ã®ã©ã€ãã©ãªã䜿çšããŠå®è£
ãããŸããããã§ã¯ã泚éã®ãããã§ããªããžã§ã¯ããèªåçã«çœ®ãæããããŸãã
public class A { @Inject B b; public A() { inject(); } }
ãã®ã¡ã«ããºã ãšAndroidã§ã®ãã®ã¢ããªã±ãŒã·ã§ã³ã®è©³çŽ°ã«ã€ããŠã¯ã次ã®èšäºãã芧ãã ãã
ãDaggerã®äŸã䜿çšããŠãäŸåé¢ä¿ã®æ³šå
¥ãç解ãããã¬ãŒ2
Dagger 2ã¯ãDIãå®è£
ããããã«Googleã«ãã£ãŠäœæãããã©ã€ãã©ãªã§ãã ã³ãŒãçæã«ãããäž»ãªå©ç¹ãã€ãŸã ãã¹ãŠã®ãšã©ãŒã¯ã³ã³ãã€ã«æ®µéã§è¡šç€ºãããŸãã ããã«ã¯ã
ãã¬ãŒ2ã«é¢ããè¯ãèšäºããããŸãã
å
¬åŒããŒãžã
ã³ãŒããã¹ã«é¢ããé©åãªæ瀺ãèªãããšãã§ããŸãã
Dagger 2ãã€ã³ã¹ããŒã«ããã«ã¯ãbuild.gradleãç·šéããŸãã
build.gradle apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:21.0.3' compile 'com.google.dagger:dagger:2.0-SNAPSHOT' apt 'com.google.dagger:dagger-compiler:2.0-SNAPSHOT' provided 'org.glassfish:javax.annotation:10.0-b28' }
Dagger IntelliJ Pluginãã©ã°ã€ã³ãã€ã³ã¹ããŒã«ããããšã匷ããå§ãããŸãã 泚å
¥ãçºçããå Žæãšå Žæãã移åããã®ã«åœ¹ç«ã¡ãŸãã
Dagger IntelliJãã©ã°ã€ã³ Dagger 2ã¯ãã¢ãžã¥ãŒã«ã¡ãœããããã¡ãœãããå®è£
ããããã«ãªããžã§ã¯ããååŸããŸãïŒã¡ãœããã«ã¯ãã¢ãããŒã·ã§ã³ã®
æäŸ ãã¢ãžã¥ãŒã«-
ã¢ãžã¥ãŒã«ã§ããŒã¯ããå¿
èŠããã
ãŸã ïŒããã¢ãããŒã·ã§ã³ä»ã
Injectã¯ã©ã¹ã®ã³ã³ã¹ãã©ã¯ã¿ãŒã䜿çšããŠãªããžã§ã¯ããäœæããŸãã äŸïŒ
@Module public class ModelModule { @Provides @Singleton ApiInterface provideApiInterface() { return ApiModule.getApiInterface(); } }
ãŸãã¯
public class RepoBranchesMapper @Inject public RepoBranchesMapper() {} }
åã蟌ããã£ãŒã«ãã¯ã
Injectã¢ãããŒã·ã§ã³ã§ç€ºãããŸãã
@Inject protected ApiInterface apiInterface;
ããã2ã€ã®ãã®ã¯ãã³ã³ããŒãã³ãïŒ@ComponentïŒã䜿çšããŠæ¥ç¶ãããŸãã ãããã¯ããªããžã§ã¯ããã©ãããååŸããã©ãã«æ³šå
¥ãããã瀺ããŸãïŒã¡ãœããã®æ³šå
¥ïŒã äŸïŒ
@Singleton @Component(modules = {ModelModule.class}) public interface AppComponent { void inject(ModelImpl dataRepository); }
Dagger 2ã§ã¯ã1ã€ã®ã³ã³ããŒãã³ãïŒAppComponentïŒãš3ã€ã®ã¢ãžã¥ãŒã«ãç°ãªãã¬ã€ã€ãŒïŒã¢ãã«ããã¬ãŒã³ããŒã·ã§ã³ããã¥ãŒïŒã«äœ¿çšããŸãã
æ
åœè
@Singleton @Component(modules = {ModelModule.class, PresenterModule.class, ViewModule.class}) public interface AppComponent { void inject(ModelImpl dataRepository); void inject(BasePresenter basePresenter); void inject(RepoListPresenter repoListPresenter); void inject(RepoInfoPresenter repoInfoPresenter); void inject(RepoInfoFragment repoInfoFragment); }
ã¢ãã«
ã¢ãã«ã¬ã€ã€ãŒã®å ŽåããããŒã管çããããã®ApiInterfaceãš2ã€ã®ã¹ã±ãžã¥ãŒã©ãæäŸããå¿
èŠããããŸãã ã¹ã±ãžã¥ãŒã©ã®å ŽåãDaggerãäŸåé¢ä¿ã°ã©ããææ¡ã§ããããã«ã
ååä»ã泚éã䜿çšããå¿
èŠããããŸãã
ModelModule @Provides @Singleton ApiInterface provideApiInterface() { return ApiModule.getApiInterface(Const.BASE_URL); } @Provides @Singleton @Named(Const.UI_THREAD) Scheduler provideSchedulerUI() { return AndroidSchedulers.mainThread(); } @Provides @Singleton @Named(Const.IO_THREAD) Scheduler provideSchedulerIO() { return Schedulers.io(); }
çºè¡šè
ãã¬ãŒã³ã¿ãŒã¬ã€ã€ãŒã«ã¯ãModelãšCompositeSubscriptionãããã³ããããŒãæäŸããå¿
èŠããããŸãã 泚éä»ãã³ã³ã¹ãã©ã¯ã¿ãŒã䜿çšããŠãã¢ãžã¥ãŒã«ãããããŒãä»ããŠModelããã³CompositeSubscriptionãæäŸããŸãã
ãã¬ãŒã³ã¿ãŒã¢ãžã¥ãŒã« public class PresenterModule { @Provides @Singleton Model provideDataRepository() { return new ModelImpl(); } @Provides CompositeSubscription provideCompositeSubscription() { return new CompositeSubscription(); } }
泚éä»ãã³ã³ã¹ãã©ã¯ã¿ãŒã䜿çšããããããŒã®äŸ public class RepoBranchesMapper implements Func1<List<BranchDTO>, List<Branch>> { @Inject public RepoBranchesMapper() { } @Override public List<Branch> call(List<BranchDTO> branchDTOs) { List<Branch> branches = Observable.from(branchDTOs) .map(branchDTO -> new Branch(branchDTO.getName())) .toList() .toBlocking() .first(); return branches; } }
衚瀺ãã
Viewã¬ã€ã€ãŒãšãã¬ãŒã³ã¿ãŒã®çŽ¹ä»ã«ãããç¶æ³ã¯ããè€éã«ãªããŸãã ãã¬ãŒã³ã¿ãŒãäœæãããšããã³ã³ã¹ãã©ã¯ã¿ãŒã§Viewã€ã³ã¿ãŒãã§ã€ã¹ãæž¡ããŸãã ãããã£ãŠãDaggerã«ã¯ããã®ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
ãã€ãŸããã©ã°ã¡ã³ããžã®ãªã³ã¯ãå¿
èŠã§ãã å¥ã®æ¹æ³ãšããŠããã¬ãŒã³ã¿ãŒã€ã³ã¿ãŒãã§ã€ã¹ãå€æŽããonCreateã«ãã¥ãŒãªã³ã¯ãæž¡ãããšãã§ããŸãã äž¡æ¹ã®ã±ãŒã¹ãæ€èšããŸãã
ãã¥ãŒãªã³ã¯ãæž¡ããŸãã
RepoListViewã€ã³ã¿ãŒãã§ãŒã¹ãå®è£
ããRepoListFragmentãã©ã°ã¡ã³ããããã
RepoListPresenterã¯ããã®RepoListViewãã³ã³ã¹ãã©ã¯ã¿ãŒãžã®å
¥åãšããŠåãå
¥ããŸãã RepoListFragmentã«RepoListPresenterãå®è£
ããå¿
èŠããããŸãã ãã®ãããªã¹ããŒã ãå®è£
ããã«ã¯ãã³ã³ã¹ãã©ã¯ã¿ãŒã§RepoListViewã€ã³ã¿ãŒãã§ã€ã¹ãžã®ãªã³ã¯ãåãå
¥ããæ°ããã³ã³ããŒãã³ããšæ°ããã¢ãžã¥ãŒã«ãäœæããå¿
èŠããããŸãã ãã®ã¢ãžã¥ãŒã«ã§ã¯ãïŒRepoListViewã€ã³ã¿ãŒãã§ã€ã¹ãžã®ãªã³ã¯ã䜿çšããŠïŒãã¬ãŒã³ã¿ãŒãäœæãããã©ã°ã¡ã³ãã«åã蟌ã¿ãŸãã
ãã©ã°ã¡ã³ãã§ã®æ³šå
¥ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); DaggerViewComponent.builder() .viewDynamicModule(new ViewDynamicModule(this)) .build() .inject(this); }
æå @Singleton @Component(modules = {ViewDynamicModule.class}) public interface ViewComponent { void inject(RepoListFragment repoListFragment); }
ã¢ãžã¥ãŒã« @Module public class ViewDynamicModule { RepoListView view; public ViewDynamicModule(RepoListView view) { this.view = view; } @Provides RepoListPresenter provideRepoListPresenter() { return new RepoListPresenter(view); } }
å®éã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãå€ãã®ã€ã³ãžã§ã¯ã·ã§ã³ãšã¢ãžã¥ãŒã«ãããããããšã³ãã£ãã£ããšã«ç°ãªãã³ã³ããŒãã³ããäœæããããšã¯ã
ç¥ãªããžã§ã¯ãã®äœæãé²ãããã®çŽ æŽãããã¢ã€ãã¢ã§ãã
çºè¡šè
ã³ãŒããå€æŽããŸãã
äžèšã®æ¹æ³ã§ã¯ãããã€ãã®ãã¡ã€ã«ãšå€ãã®ã¢ã¯ã·ã§ã³ãäœæããå¿
èŠããããŸãã ãã®å Žåãã¯ããã«ç°¡åãªæ¹æ³ããããŸããã³ã³ã¹ãã©ã¯ã¿ãå€æŽããonCreateã®ã€ã³ã¿ãŒãã§ã€ã¹ãžã®ãªã³ã¯ã転éããŸãã
ã³ãŒãïŒ
ãã©ã°ã¡ã³ãã§ã®æ³šå
¥ @Inject RepoInfoPresenter presenter; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); App.getComponent().inject(this); presenter.onCreate(this, getRepositoryVO()); }
ã¢ãžã¥ãŒã« @Module public class ViewModule { @Provides RepoInfoPresenter provideRepoInfoPresenter() { return new RepoInfoPresenter(); } }
Dagger 2ã®å®è£
ãå®äºããããã¢ããªã±ãŒã·ã§ã³ã®ãã¹ãã«ç§»ããŸãããã
ã¹ããã4.ãŠããããã¹ã
ãã¹ãã¯é·ãéããœãããŠã§ã¢éçºããã»ã¹ã®äžå¯æ¬ ãªéšåã§ããã
ãŠã£ãããã£ã¢ã§ã¯ãå€ãã®çš®é¡ã®ãã¹ããç¹å®ããŠããŸã ããŸã
ãåäœãã¹ããæ±ããŸãã
åäœãã¹ãã¯ãããã°ã©ã ã®ãœãŒã¹ã³ãŒãã®åã
ã®ã¢ãžã¥ãŒã«ã®æ£ç¢ºæ§ã確èªã§ããããã°ã©ãã³ã°ããã»ã¹ã§ãã
ã¢ã€ãã¢ã¯ãéèŠãªã¡ãœããããšã«ãã¹ããäœæããããšã§ãã ããã«ããã次ã®ã³ãŒãå€æŽããªã°ã¬ãã·ã§ã³ã«ã€ãªãã£ãŠãããã©ãããã€ãŸããããã°ã©ã ã®ãã¹ãæžã¿ã®å Žæã§ãšã©ãŒãçºçããŠãããã©ããããã°ãã確èªã§ãããã®ãããªãšã©ãŒã®æ€åºãšé€å»ã容æã«ãªããŸãã
ãã¹ãŠã®ã³ã³ããŒãã³ããçžäºã«äœçšãããããå®å
šã«åé¢ããããã¹ããäœæããããšã¯ã§ããŸããã åäœãã¹ãã§ã¯ãmokamiã«å²ãŸãã1ã€ã®ã¢ãžã¥ãŒã«ã®åäœããã§ãã¯ããããšãæå³ããŸãã çµ±åãã¹ãã§ããã€ãã®å®éã®ã¢ãžã¥ãŒã«ã®çžäºäœçšã確èªããŸãã
ã¢ãžã¥ãŒã«ã®çžäºäœçšã¹ããŒã ïŒ

ããããŒãã¹ãã®äŸïŒã°ã¬ãŒã¢ãžã¥ãŒã«-æªäœ¿çšãç·-mokiãé-ãã¹ãäžã®ã¢ãžã¥ãŒã«ïŒïŒ

ã€ã³ãã©
ããŒã«ãšãã¬ãŒã ã¯ãŒã¯ã«ããããã¹ãã®äœæãšãµããŒãã容æã«ãªããŸãã èµ€ããã¹ããšã®ããŒãžãé²ãCIãµãŒããŒã¯ãmasterãã©ã³ãã§äºæããã«ãã¹ããäžæããå¯èœæ§ãåçã«æžãããŸãã ãã¹ããšãã€ããªãŒãã«ããèªåçã«å®è¡ãããšãåé¡ãæ©æã«ç¹å®ã§ããŸãã ãã®åçã¯
ãã§ã€ã«ãã¡ãŒã¹ããšåŒã°ã
ãŸã ã
ãã¹ãç°å¢ã«ã€ããŠã¯ã
Androidã§ã®ãã¹ãïŒRobolectric + Jenkins +Jaooã®èšäºãã芧ãã ãã ã å°æ¥çã«ã¯ã
Robolecricã䜿çšããŠãã¹ããäœæãã
mockitoã䜿çšããŠã¢ãã¯ãäœæãã
Jaooã䜿çšããŠã³ãŒãã®ã«ãã¬ããžããã¹ãã§ãã¹ãããŸãã
MVPãã¿ãŒã³ã䜿çšãããšãã³ãŒãã®ãã¹ããè¿
éãã€å¹ççã«èšè¿°ã§ããŸãã Dagger 2ã®å©ããåããŠãå®éã®ãªããžã§ã¯ãããã¹ãmokiã«çœ®ãæããŠãã³ãŒããå€éšããéé¢ã§ããŸãã ãã®ããã«ããã¹ãã¢ãžã¥ãŒã«ãåãããã¹ãã³ã³ããŒãã³ãã䜿çšããŸãã ã³ã³ããŒãã³ãã¯ããã¹ãã¢ããªã±ãŒã·ã§ã³ã§çœ®ãæããããŸãããã¹ãã¢ããªã±ãŒã·ã§ã³ã¯ãããŒã¹ãã¹ãã¯ã©ã¹ã®
Configã¢ãããŒã·ã§ã³ïŒapplication = TestApplication.classïŒã䜿çšããŠ
èšå®ããŸãã
JaCoCoã³ãŒãã«ãã¬ããž
éå§ããåã«ããã¹ãããã¡ãœãããšã«ãã¬ããžãã¹ãã®å²åã®èšç®æ¹æ³ã決å®ããå¿
èŠããããŸãã ãããè¡ãã«ã¯ããã¹ãçµæã«é¢ããã¬ããŒããçæããJaCoCoã©ã€ãã©ãªã䜿çšããŸãã
ææ°ã®Android Studio
ã¯ãããã«äœ¿çšã§ãã
ã³ãŒãã«ãã¬ããžããµããŒãããŠããŸãããŸãã¯ãbuild.gradleã«æ¬¡ã®è¡ãè¿œå ããŠæ§æã§ããŸãã
build.gradle apply plugin: 'jacoco' jacoco { toolVersion = "0.7.1.201405082137" } def coverageSourceDirs = [ '../app/src/main/java' ] task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") { group = "Reporting" description = "Generate Jacoco coverage reports" classDirectories = fileTree( dir: '../app/build/intermediates/classes/debug', excludes: ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/*$ViewBinder*.*', //DI '**/*_MembersInjector*.*', //DI '**/*_Factory*.*', //DI '**/testrx/model/dto/*.*', //dto model '**/testrx/presenter/vo/*.*', //vo model '**/testrx/other/**', '**/BuildConfig.*', '**/Manifest*.*', '**/Lambda$*.class', '**/Lambda.class', '**/*Lambda.class', '**/*Lambda*.class'] ) additionalSourceDirs = files(coverageSourceDirs) sourceDirectories = files(coverageSourceDirs) executionData = files('../app/build/jacoco/testDebugUnitTest.exec') reports { xml.enabled = true html.enabled = true } }
é€å€ãããã¯ã©ã¹ã«æ³šæããŠãã ããïŒDagger 2ãšDTOããã³VOã¢ãã«ã«é¢é£ãããã¹ãŠãåé€ããŸããã
jacocoïŒjacocoTestReportã段éçã«å®è¡ïŒãå®è¡ããçµæã確èªããŸãã

ããã§ããã¹ãã®æ°ãšçæ³çã«äžèŽããã«ãã¬ããžã®å²åãã€ãŸã0ïŒ
=ïŒã«ãªããŸããããã®ç¶æ³ãä¿®æ£ããŸãããã
ã¢ãã«
ã¢ãã«å±€ã§ã¯ãã¬ãããã£ããïŒApiInterfaceïŒèšå®ã®æ£ç¢ºæ§ãã¯ã©ã€ã¢ã³ãäœæã®æ£ç¢ºæ§ãModelImplã®æäœã確èªããå¿
èŠããããŸãã
ã³ã³ããŒãã³ãã¯åé¢ããŠã¹ãã£ã³ããå¿
èŠãããããããµãŒããŒããšãã¥ã¬ãŒãããå¿
èŠããããã©ããã確èªããã«ã¯ã
MockWebServerããããæ¯æŽããŸãã ãµãŒããŒã®å¿çãæ§æããæ¹é èŠæ±ã確èªããŸãã
ã¢ãã«å±€ã¹ããŒã ããã¹ããå¿
èŠãªã¯ã©ã¹ã¯èµ€ã§ããŒã¯ãããŠããŸã Dagger 2ã®ãã¹ãã¢ãžã¥ãŒã« @Module public class ModelTestModule { @Provides @Singleton ApiInterface provideApiInterface() { return mock(ApiInterface.class); } @Provides @Singleton @Named(Const.UI_THREAD) Scheduler provideSchedulerUI() { return Schedulers.immediate(); } @Provides @Singleton @Named(Const.IO_THREAD) Scheduler provideSchedulerIO() { return Schedulers.immediate(); } }
è©ŠéšäŸ public class ApiInterfaceTest extends BaseTest { private MockWebServer server; private ApiInterface apiInterface; @Before public void setUp() throws Exception { super.setUp(); server = new MockWebServer(); server.start(); final Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { if (request.getPath().equals("/users/" + TestConst.TEST_OWNER + "/repos")) { return new MockResponse().setResponseCode(200) .setBody(testUtils.readString("json/repos")); } else if (request.getPath().equals("/repos/" + TestConst.TEST_OWNER + "/" + TestConst.TEST_REPO + "/branches")) { return new MockResponse().setResponseCode(200) .setBody(testUtils.readString("json/branches")); } else if (request.getPath().equals("/repos/" + TestConst.TEST_OWNER + "/" + TestConst.TEST_REPO + "/contributors")) { return new MockResponse().setResponseCode(200) .setBody(testUtils.readString("json/contributors")); } return new MockResponse().setResponseCode(404); } }; server.setDispatcher(dispatcher); HttpUrl baseUrl = server.url("/"); apiInterface = ApiModule.getApiInterface(baseUrl.toString()); } @Test public void testGetRepositories() throws Exception { TestSubscriber<List<RepositoryDTO>> testSubscriber = new TestSubscriber<>(); apiInterface.getRepositories(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()); } @After public void tearDown() throws Exception { server.shutdown(); } }
ã¢ãã«ããã¹ãããããã«ãApiInterfaceãã¯ã€ãããæ£ããåäœã確èªããŸãã
ModelImplã®ãµã³ãã«ãã¹ã @Test public void testGetRepoBranches() { BranchDTO[] branchDTOs = testUtils.getGson().fromJson(testUtils.readString("json/branches"), BranchDTO[].class); when(apiInterface.getBranches(TestConst.TEST_OWNER, TestConst.TEST_REPO)).thenReturn(Observable.just(Arrays.asList(branchDTOs))); TestSubscriber<List<BranchDTO>> testSubscriber = new TestSubscriber<>(); model.getRepoBranches(TestConst.TEST_OWNER, TestConst.TEST_REPO).subscribe(testSubscriber); testSubscriber.assertNoErrors(); testSubscriber.assertValueCount(1); List<BranchDTO> actual = testSubscriber.getOnNextEvents().get(0); assertEquals(3, actual.size()); assertEquals("QuickStart", actual.get(0).getName()); assertEquals("94870e23f1cfafe7201bf82985b61188f650b245", actual.get(0).getCommit().getSha()); }
Jacocoã®ã«ãã¬ããžã確èªããŸãã

çºè¡šè
ãã¬ãŒã³ã¿ãŒã¬ã€ã€ãŒã§ã¯ãããããŒã®äœæ¥ãšãã¬ãŒã³ã¿ãŒã®äœæ¥ããã¹ãããå¿
èŠããããŸãã
Presenterã¬ã€ã€ãŒã®ã¬ã€ã¢ãŠãããã¹ããå¿
èŠãªã¯ã©ã¹ã¯èµ€ã§ããŒã¯ãããŠããŸã ããããŒã§ã¯ããã¹ãŠãéåžžã«ç°¡åã§ãã ãã¡ã€ã«ããjsonãèªã¿åããå€æããŠç¢ºèªããŸãã
ãã¬ãŒã³ã¿ãŒã䜿çšããŠãã¢ãã«ãèµ·åãããã¥ãŒã«å¿
èŠãªã¡ãœããã®åŒã³åºãã確èªããŸãã ãŸããonSubscribeãšonStopã®æ£ç¢ºãã確èªããå¿
èŠããããŸããããã¯ããµãã¹ã¯ãªãã·ã§ã³ãã€ã³ã¿ãŒã»ããããŠisUnsubscribedã確èªããããã§ãã
ãã¬ãŒã³ã¿ãŒã¬ã€ã€ãŒã§ã®ãã¹ãäŸ @Before public void setUp() throws Exception { super.setUp(); component.inject(this); activityCallback = mock(ActivityCallback.class); mockView = mock(RepoListView.class); repoListPresenter = new RepoListPresenter(mockView, activityCallback); doAnswer(invocation -> Observable.just(repositoryDTOs)) .when(model) .getRepoList(TestConst.TEST_OWNER); doAnswer(invocation -> TestConst.TEST_OWNER) .when(mockView) .getUserName(); } @Test public void testLoadData() { repoListPresenter.onCreateView(null); repoListPresenter.onSearchButtonClick(); repoListPresenter.onStop(); verify(mockView).showRepoList(repoList); } @Test public void testSubscribe() { repoListPresenter = spy(new RepoListPresenter(mockView, activityCallback));
JaCoCoã®å€æŽãåç
§ããŠãã ããã

衚瀺ãã
ãã¥ãŒã¬ã€ã€ãŒããã¹ããããšãã¯ããã©ã°ã¡ã³ããããã¬ãŒã³ã¿ãŒã©ã€ããµã€ã¯ã«ã¡ãœããã®åŒã³åºãã®ã¿ããã§ãã¯ããå¿
èŠããããŸãã ãã¹ãŠã®ããžãã¯ã¯ãã¬ãŒã³ã¿ãŒã«å«ãŸããŠããŸãã
ã¬ã€ã€ãŒãã¥ãŒå³ããã¹ããå¿
èŠãªã¯ã©ã¹ã¯èµ€ã§ããŒã¯ãããŠããŸã ãã©ã°ã¡ã³ããã¹ãã®äŸ @Test public void testOnCreateViewWithBundle() { repoInfoFragment.onCreateView(LayoutInflater.from(activity), (ViewGroup) activity.findViewById(R.id.container), bundle); verify(repoInfoPresenter).onCreateView(bundle); } @Test public void testOnStop() { repoInfoFragment.onStop(); verify(repoInfoPresenter).onStop(); } @Test public void testOnSaveInstanceState() { repoInfoFragment.onSaveInstanceState(null); verify(repoInfoPresenter).onSaveInstanceState(null); }
æçµãã¹ãç¯å²ïŒ

çµè«ãŸãã¯ç¶ç¶...
èšäºã®ç¬¬2éšã§ã¯ãDagger 2ã®å®è£
ã調ã¹ããŠãããã³ãŒãããã¹ãã§ã«ããŒããŸããã MVPãšã€ã³ãžã§ã¯ã·ã§ã³ã€ã³ãžã§ã¯ã·ã§ã³ã®ãããã§ãã¢ããªã±ãŒã·ã§ã³ã®ãã¹ãŠã®éšåã®ãã¹ãããã°ããäœæã§ããŸããã
ãã¹ãŠã®ã³ãŒãã¯githubã§å
¥æã§ããŸã ã èšäºã¯
nnesterovã®ç©æ¥µçãªåå ã§æžãããŸããã 次ã®ããŒãã§ã¯ãçµ±åãã¹ããšæ©èœãã¹ããããã³TDDã«ã€ããŠèª¬æããŸãã
æŽæ°Androidã¢ããªã段éçã«æ§ç¯ãããããŒã3