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
きりみん
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
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
190
「エンジニアインターン、どうやって取った?」準備のリアルを語るLT会 Progate BAR
akiomatic
0
130
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
340
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
260
Webフレームワークの ベンチマークについて
yusukebe
0
170
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.6k
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
240
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
700
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
13
5k
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
350
Contextとはなにか
chiroruxx
1
330
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
160
Featured
See All Featured
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
150
AI: The stuff that nobody shows you
jnunemaker
PRO
8
710
My Coaching Mixtape
mlcsv
0
150
Code Review Best Practice
trishagee
74
20k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
410
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.5k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
230
Paper Plane
katiecoart
PRO
1
51k
The untapped power of vector embeddings
frankvandijk
2
1.8k
Statistics for Hackers
jakevdp
799
230k
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
620
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ͷ ςετΛ͡ΊΑ͏ʂ
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠