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.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
AWS Transform Customによる Spring Boot 2.xから4.xへのVerUp
satoshi256kbyte
2
100
自動化したのに回らない テスト運用の壁―AI時代の品質責任と生産性
mfunaki
0
430
いまどきの Codex で開発する visionOS アプリの開発スタイルについて
karad
0
160
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
390
プロポーザルを書いてもらう
pvcresin
0
570
属人化した知識を、 AIが辿れる地図にする
pkshadeck
PRO
1
200
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
270
2年かけて Deno に DOMMatrix を実装した話 / How I implemented DOMMatrix in Deno over two years
petamoriken
0
220
関東Kaggler会_NVIDIA_Nemotron_コンペ_振り返り
rick_ds
0
710
jsmini JavaScript Engine を作ってみた話
yosuke_furukawa
PRO
0
350
TSX の <Hoge<Fuga>> という構文に驚いた話 / tsx-type-argument-syntax
kanaru0928
0
240
「つくるAI」だけではバグは見つからない ~テストに必要な「見つけるAI」を分離させる戦略~
mfunaki
0
160
Featured
See All Featured
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
440
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
280
Thoughts on Productivity
jonyablonski
76
5.3k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
220
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.1k
Become a Pro
speakerdeck
PRO
31
6.2k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
The Invisible Side of Design
smashingmag
301
52k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
360
Deep Space Network (abreviated)
tonyrice
0
270
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
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ͷ ςετΛ͡ΊΑ͏ʂ
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠