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
きりみん
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
SalesForceを内製化!? ~ HR事業を支える顧客管理基盤のインフラを大公開 ~
oku053
0
140
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
580
Are APIs Still Relevant in the AI Era?
soyuka
0
430
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
130
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
1
2.2k
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
340
AI が書く Go コードの品質を劇的に向上させる Linter: “declscope”
mpyw
0
480
Java 27新機能 / Java 27 new features
kishida
2
200
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
5
4.9k
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
190
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
390
ジョジョの世界をXRで再現する
mofurune
0
130
Featured
See All Featured
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3k
What does AI have to do with Human Rights?
axbom
PRO
1
2.4k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
2
2.9k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
840
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
Context Engineering - Making Every Token Count
addyosmani
9
1.2k
Making the Leap to Tech Lead
cromwellryan
135
10k
The Invisible Side of Design
smashingmag
301
52k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
370
Google's AI Overviews - The New Search
badams
0
1.6k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Building Flexible Design Systems
yeseniaperezcruz
330
41k
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ͷ ςετΛ͡ΊΑ͏ʂ
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠