Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Refactoring-mvp-Anti-Pattern
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
きりみん
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.7k
What are AtCoder and competitive programming
kirimin
0
10k
バーチャル男声幼女プログラマーとして活動した1年間の振り返り
kirimin
0
1.2k
アプリエンジニアでも神絵師になりたい!
kirimin
4
5.5k
Watashi ni Kotlin ga maiorita
kirimin
0
610
NEMのAPIとモザイクであそぼう
kirimin
0
430
はじめようきれいなコード
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
iOS開発×AI駆動開発 〜最近使って便利だったスキルの話〜
nogu66
0
140
Intent as Code
shoppingjaws
6
740
20260828_品質と開発生産性を両立させる、AI時代のE2Eテストの考え方
magicpod
0
180
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
910
不幸な GC
chencmd
0
900
Laravelのアプリケーションをどこにデプロイするか #ツナギメオフライン.9
akase244
0
120
「AI時代、配布するPythonコードをどう守るか: 難読化の実験と判断軸」 #PyconJP2026
pkshadeck
PRO
2
190
Vibes Containers 〜AIで変わるコンテナ設計と運用〜
tkikuc
3
520
新人はどこまで自力でやり、どこからAIに頼るべきか/エンジニア育成に向き合う_先輩たちの悩みと知見共有会
toppan_digital_dev
1
590
The Good Stuff, Not the Slop: Engineering High-Quality Android Apps with Modern AI Tooling
danybony
1
230
スマート反転とウェブアクセシビリティ
camiha
0
180
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
2
270
Featured
See All Featured
A Modern Web Designer's Workflow
chriscoyier
699
190k
How to build a perfect <img>
jonoalderson
1
6k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
190
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
470
Evolving SEO for Evolving Search Engines
ryanjones
0
280
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
520
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
520
WENDY [Excerpt]
tessaabrams
13
39k
Paper Plane
katiecoart
PRO
3
53k
Visualization
eitanlees
152
17k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2.8k
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ͷ ςετΛ͡ΊΑ͏ʂ
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠