Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Refactoring-mvp-Anti-Pattern
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
きりみん
May 10, 2017
Programming
2.8k
6
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Refactoring-mvp-Anti-Pattern
きりみん
May 10, 2017
More Decks by きりみん
See All by きりみん
AndroidエンジニアがRailsにチャレンジしてる理由
kirimin
1
1.6k
What are AtCoder and competitive programming
kirimin
0
10k
バーチャル男声幼女プログラマーとして活動した1年間の振り返り
kirimin
0
1.1k
アプリエンジニアでも神絵師になりたい!
kirimin
4
5.5k
Watashi ni Kotlin ga maiorita
kirimin
0
600
NEMのAPIとモザイクであそぼう
kirimin
0
420
はじめようきれいなコード
kirimin
8
3.2k
Material Components for Android触ってみる
kirimin
7
2.1k
[社内LT]あたらしいMaterial Design
kirimin
1
1.8k
Other Decks in Programming
See All in Programming
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
130
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
140
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.1k
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
780
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
160
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
140
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
260
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
130
Webフレームワークの ベンチマークについて
yusukebe
0
170
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
700
Vite+ Unified Toolchain for the Web
naokihaba
0
310
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
120
Featured
See All Featured
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
840
Code Review Best Practice
trishagee
74
20k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
240
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
780
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
580
Typedesign – Prime Four
hannesfritz
42
3.1k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
200
Transcript
MVPΞϯνύλʔϯΛվળͯ͠ PresenterͷςετΛॻ͜͏ Android Testing Bootcamp #6 @kirimin
@kirimin ͖ΓΈΜ ɾϑϦʔϥϯεͷAndroidΤϯδχΞ ɾKotlin͍͖ͩ͢Ϛϯ ɾMVP͓͡͞Μ
MVPͬͯ·͔͢ʁ • MVP = Model View Presenter • Androidք۾ͰMVP͕ʹͳͬͨͷ2͘Β͍લɻ •
MVPΛ࠾༻͍ͯ͠ΔϓϩδΣΫτ݁ߏݟ͔͚Δҹɻ
MVPͬͯ·͔͢ʁ • MVP = Model View Presenter • Androidք۾ͰMVP͕ʹͳͬͨͷ2͘Β͍લɻ •
MVPΛ࠾༻͍ͯ͠ΔϓϩδΣΫτ݁ߏݟ͔͚Δҹɻ • ओͳϝϦοτͱͯ͠Α͘ςετͷॻ͖͕͢͞ ڍ͛ΒΕ͍ͯͨɻ
MVP࠾༻ϓϩδΣΫτ ͋Δ͋Δ ݸਓతͳܦݧʹΑΔ
ςετ͕ॻ͔Ε͍ͯͳ͍
Presenter͕ActivityFragmentΛ ࢀর͍ͯ͠Δ
ViewʹϩδοΫ͕ॻ͔Ε͍ͯͨΓ Presenter͕ViewΛ ॻ͖͍͑ͯͨΓ͢Δ
PresenterͱViewͷ ่͚͕յ͍ͯ͠Δ…
͜ΕɺΫϥε͕྾ͯ͠ ίʔυ͕͍ʹ͘͘ͳͬͨ ͚ͩ͡ΌͶ…ʁ
Ͳ͏ͯ͜͠Μͳ͜ͱʹ…
ݪҼ ViewͱPresenterͷ͚͕ ग़དྷ͍ͯͳ͍
Ͳ͏͢Ε͍͍͔
ViewͱPresenterΛ͢Δ • ActivityFragmentΛPresenter͕ࢀরग़ དྷͯ͠·͏ͱɺ݁ہ่͚͕յ͢Δ
ViewͱPresenterΛ͢Δ • ActivityFragmentΛPresenter͕ࢀরग़ དྷͯ͠·͏ͱɺ݁ہ่͚͕յ͢Δ • PresenterʹViewૢ࡞σʔλΞΫηε͕ ॻ͔Ε͍ͯΔͱϞοΫԽ͕ग़དྷͣPresenterͷ ςετΛॻ͘ͷ͘͠ͳΔ
ViewͱPresenterΛ͢Δ • ActivityFragmentΛPresenter͕ࢀরग़ དྷͯ͠·͏ͱɺ݁ہ่͚͕յ͢Δ • PresenterʹViewૢ࡞σʔλΞΫηε͕ ॻ͔Ε͍ͯΔͱϞοΫԽ͕ग़དྷͣPresenterͷ ςετΛॻ͘ͷ͘͠ͳΔ • ·ͣViewΛநԽ͠Α͏
αϯϓϧίʔυ
·ͣΞϯνύλʔϯ
public class MainActivity extends AppCompatActivity { private MainPresenter presenter;
private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); presenter = new MainPresenter(this, new MainUseCase()); presenter.onCreate(); button = (Button) findViewById(R.id.button); button.setVisibility(View.INVISIBLE); button.setOnClickListener(v -> presenter.onButtonClick()); presenter.loadButtonText(); } public void setButtonText(String buttonText) { if (TextUtils.isEmpty(buttonText)) { button.setVisibility(View.INVISIBLE); } else { button.setText(buttonText); button.setVisibility(View.VISIBLE); } } }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); … presenter.loadButtonText(); }
public void setButtonText(String buttonText) { if (TextUtils.isEmpty(buttonText)) { button.setVisibility(View.INVISIBLE); } else { button.setText(buttonText); button.setVisibility(View.VISIBLE); } } } 7JFX͕1SFTFOUFSʹ ࢦࣔΛग़͍ͯ͠Δ 7JFXଆͰϩδοΫΛ ͍࣋ͬͯΔ
public class MainPresenter { private MainActivity view; private MainUseCase
useCase; public MainPresenter(MainView view, MainUseCase useCase) { this.view = view; this.useCase = useCase; } public void onCreate() { } public void onButtonClick() { view.startActivity(new Intent(view, SubActivity.class)); } public void loadButtonText() { useCase.loadButtonText() .subscribe(s -> { view.setButtonText(s); }, throwable -> { }); } }
public class MainPresenter { private MainActivity view; private MainUseCase
useCase; … public void onButtonClick() { view.startActivity( new Intent(view,ɹSubActivity.class)); } public void loadButtonText() { useCase.loadButtonText() .subscribe(s -> { view.setButtonText(s); }, throwable -> { }); } } 7JFXΛ"DUJWJUZͱͯ͠ ͍࣋ͬͯΔ "DUJWJUZΛ1SFTFOUFS͕ ૢ࡞͍ͯ͠Δ
ϦϑΝΫλޙ
interface MainView { void startSubActivity(); void showButton(); void hideButton();
void setButtonText(String s); class MainActivity extends AppCompatActivity implements MainView { private MainPresenter presenter; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); button.setOnClickListener(v -> presenter.onButtonClick()); presenter = new MainPresenter(this, new MainUseCase()); presenter.onCreate(); } @Override public void startSubActivity() { startActivity(new Intent(this, SubActivity.class)); } @Override public void showButton() { button.setVisibility(View.INVISIBLE); } @Override public void hideButton() { button.setVisibility(View.INVISIBLE); } @Override public void setButtonText(String buttonText) { button.setText(buttonText); } } }
interface MainView { void startSubActivity(); void showButton(); void hideButton();
void setButtonText(String s); class MainActivity extends AppCompatActivity implements MainView { "DUJWJUZͷૢ࡞Λ *OUFSGBDFͱͯ͠நԽ "DUJWJUZ7JFXΛ࣮
protected void onCreate(Bundle savedInstanceState) { … presenter.onCreate(); } @Override
public void startSubActivity() { startActivity(new Intent(this, SubActivity.class)); } @Override public void showButton() { button.setVisibility(View.VISIBLE); } @Override public void hideButton() { button.setVisibility(View.INVISIBLE); } @Override public void setButtonText(String buttonText) { button.setText(buttonText); } 7JFX1SFTFOUFSʹରͯ͠ ϥΠϑαΠΫϧͳͲͷ ΠϕϯτΛ͚ͩ͢ ϩδοΫ࣋ͨͣɺ ͍6*ૢ࡞Λ࣮ߦ͢Δ͚ͩͷ ϝιου܈Λ࣋ͭ
public class MainPresenter { private MainView view; private MainUseCase
useCase; public MainPresenter(MainView view, MainUseCase useCase) { this.view = view; this.useCase = useCase; } public void onCreate() { view.showButton(); loadButtonText(); } public void onButtonClick() { view.startSubActivity(); } private void loadButtonText() { useCase.loadButtonText() .subscribe(s -> { if (TextUtils.isEmpty(s)) { view.hideButton(); } else { view.showButton(); view.setButtonText(s); } }, throwable -> { }); } }
public class MainPresenter { private MainView view; private MainUseCase
useCase; … public void onCreate() { view.showButton(); loadButtonText(); } public void onButtonClick() { view.startSubActivity(); } private void loadButtonText() { useCase.loadButtonText() .subscribe(s -> { if (TextUtils.isEmpty(s)) { view.hideButton(); } else { view.showButton(); view.setButtonText(s); } }, throwable -> { }); } } நԽͨ͠*OUFSGBDFͱͯ͠ 7JFXΛอ࣋͢Δ ॲཧͷྲྀΕΠϕϯτΛड͚ औͬͨ1SFTFOUFS੍͕ޚ͢Δ తͳ6*ૢ࡞ WJFXͷ࣮ʹҠৡ͢Δ ϩδοΫ1SFTFOUFSଆͰ࣋ͪɺ ݁Ռͷࢦ͚ࣔͩΛ7JFXʹ͢
Presenterͷςετͷॻ͖ํ
Presenterͷςετͷॻ͖ํ • MockitoͰViewUseCaseΛϞοΫԽ • खಈͰPresenterͷϝιουΛݺͼϥΠϑαΠ ΫϧΠϕϯτΛ࠶ݱ͢ΔࣄͰը໘શମͷྲྀ ΕΛςετग़དྷΔ • Presenterͷॲཧͷ݁ՌɺViewUseCaseͷϝ ιου͕ظ௨ΓʹݺΕ͍ͯΔ͔ΛΞτ
ϓοτͱͯ͠νΣοΫ͢Δ
@RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class) public class MainPresenterTest { @Rule
public MockitoRule mockito = MockitoJUnit.rule(); @Mock MainView viewMock; @Mock MainUseCase useCaseMock; @Spy @InjectMocks MainPresenter presenter; @Test public void onCreateTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("test")); presenter.onCreate(); verify(useCaseMock, times(1)).loadButtonText(); verify(viewMock, times(2)).showButton(); verify(viewMock, times(1)).setButtonText("test"); } @Test public void buttonTextEmptyTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("")); presenter.onCreate(); verify(useCaseMock, times(1)).loadButtonText(); verify(viewMock, times(1)).showButton(); verify(viewMock, times(1)).hideButton(); verify(viewMock, never()).setButtonText(anyString()); } @Test public void onButtonClickTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("test")); presenter.onCreate(); presenter.onButtonClick(); verify(viewMock, times(1)).startSubActivity(); } }
@RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class) public class MainPresenterTest { @Rule
public MockitoRule mockito = MockitoJUnit.rule(); @Mock MainView viewMock; @Mock MainUseCase useCaseMock; @Spy @InjectMocks MainPresenter presenter; !.PDLΞϊςʔγϣϯͰ ϞοΫΛ࡞ग़དྷΔ !*OKFDU.PDLTͰ !.PDLͷΛͬͯ ΠϯελϯεΛੜग़དྷΔ .PDLJUPͷΞϊςʔγϣϯΛ ༻͢ΔͨΊͷ3VMF
@Test public void onCreateTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("test")); presenter.onCreate(); verify(useCaseMock, times(1)).loadButtonText(); verify(viewMock,
times(2)).showButton(); verify(viewMock, times(1)).setButtonText("test"); } @Test public void buttonTextEmptyTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("")); presenter.onCreate(); verify(useCaseMock, times(1)).loadButtonText(); verify(viewMock, times(1)).showButton(); verify(viewMock, times(1)).hideButton(); verify(viewMock, never()).setButtonText(anyString()); } @Test public void onButtonClickTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("test")); presenter.onCreate(); presenter.onButtonClick(); verify(viewMock, times(1)).startSubActivity(); } }
@Test public void onCreateTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("test")); presenter.onCreate(); verify(useCaseMock, times(1)).loadButtonText(); verify(viewMock,
times(2)).showButton(); verify(viewMock, times(1)).setButtonText("test"); } @Test public void buttonTextEmptyTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("")); presenter.onCreate(); verify(useCaseMock, times(1)).loadButtonText(); verify(viewMock, times(1)).showButton(); verify(viewMock, times(1)).hideButton(); verify(viewMock, never()).setButtonText(anyString()); } @Test public void onButtonClickTest() { when(useCaseMock.loadButtonText()).thenReturn(Single.just("test")); presenter.onCreate(); presenter.onButtonClick(); verify(viewMock, times(1)).startSubActivity(); } } WFSJGZͰ7JFX6TF$BTFͷ ϝιου͕ظ௨Γʹ ݺΕ͍ͯΔ͔Λςετ͢Δ .PDL͕ฦ͢Λม͑ɺ݁Ռݺ ΕΔϝιουมΘ͍ͬͯΔ ͔Λςετ͢Δ ΫϦοΫΠϕϯτΛखಈͰ࠶ݱ ͠ɺ݁ՌΛςετ͢Δ
࣮ࡍͷۀͷಋೖࣄྫ
ܦҢ • ݩʑΞϯνύλʔϯʹ͍ۙઃܭͩͬͨ • IssueΛཱͯͯઃܭվળΛఏҊ • νʔϜͰϨϏϡʔ͠ͳ͕Β৽ઃܭΛࡦఆ • ࣮ྫͱͯ͠γϯϓϧͳը໘ΛҰͭϦϑΝΫ λʴςετίʔυΛ࡞͠ɺೝࣝΛ߹Θͤͨ
ϨΠϠʔߏ View (UIΞΫηε) ↑↓ Presenter (ViewϩδοΫɾڮ͠) ↓ UseCase (ϏδωεϩδοΫɾσʔλϨΠϠʔͷΞΫηε) ↓
Repository (APIΞΫηε)
ٕज़ελοΫ ɾKotlin1.1 ɾDagger2 ɾRxJava2 ɾRetrofit ɾMockito2 ɾRobolectric
ӡ༻ • PRͷग़͍ͯΔϒϥϯνʹCircleCIͰςετ͕Δ Α͏ʹઃఆ • RobolectricΛ͍JUnitTestͱ࣮ͯ͠ߦ • طଘը໘ॱ࣍ϦϑΝΫλɺ৽نը໘ݪଇ৽ઃ ܭʴPresenterςετ͋ΓͰ࣮ •
ϞσϧΫϥεͳͲʹੵۃతʹςετΛॻ͍͍ͯ͘
ͦͷςετɺʹཱͬͯΔͷʁ
ʹཱ͍ͬͯ·͢ʂʂʂ
ޮՌ • ·ͣPresenterͷςετ͚ͩͰ͋Δͱը໘ શମ͕ςετग़དྷΔͷͰɺσάϨࢭͱͯ͠ ҆৺ײ͕͋Δ • Presenter͕fatʹͳ͍ͬͯͯɺςετ͕ॻ͔ Ε͍ͯΔͷͰϩδοΫΛϞσϧʹΓग़ͨ͠ ΓɺϦϑΝΫλ͕Γ͘͢ͳͬͨ •
࣮ࡍʹPresenterͷςετʹΑͬͯσάϨ͕ݕ ग़͞Εॿ͚ΒΕͨࣄ͕Կ͋Δ
σϝϦοτ • ͖ͪΜͱͨ͠MVPͰ࣮ʴςετΛॻ͜͏ͱ ͢Δͱɺ࣮ͬͺΓ૿͑Δ • ίʔυΛमਖ਼༷͕ͨ࣌͠มΘͬͨ࣌ʹς ετͷमਖ਼͕μϧ͍ࣄ͋Δ • ͨͩ͠มߋΛҙࣝ͢Δͱ͍͏ҙຯͰςετ ίʔυͷमਖ਼༗ҙٛͰ͋Δ
Presenter͔ΒAndroidͷ ςετΛ͡ΊΑ͏ʂ
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠