Save 37% off PRO during our Black Friday Sale! »

Refactoring-mvp-Anti-Pattern

 Refactoring-mvp-Anti-Pattern

D96c7e61d2f394f1d0af66945181a230?s=128

きりみん

May 10, 2017
Tweet

Transcript

  1. MVPΞϯνύλʔϯΛվળͯ͠ PresenterͷςετΛॻ͜͏ Android Testing Bootcamp #6 @kirimin

  2. @kirimin ͖ΓΈΜ ɾϑϦʔϥϯεͷAndroidΤϯδχΞ ɾKotlin͍͖ͩ͢Ϛϯ ɾMVP͓͡͞Μ

  3. MVP΍ͬͯ·͔͢ʁ • MVP = Model View Presenter • Androidք۾ͰMVP͕࿩୊ʹͳͬͨͷ͸2೥͘Β͍લɻ •

    MVPΛ࠾༻͍ͯ͠ΔϓϩδΣΫτ΋݁ߏݟ͔͚Δҹ৅ɻ
  4. MVP΍ͬͯ·͔͢ʁ • MVP = Model View Presenter • Androidք۾ͰMVP͕࿩୊ʹͳͬͨͷ͸2೥͘Β͍લɻ •

    MVPΛ࠾༻͍ͯ͠ΔϓϩδΣΫτ΋݁ߏݟ͔͚Δҹ৅ɻ • ओͳϝϦοτͱͯ͠Α͘ςετͷॻ͖΍͕͢͞ ڍ͛ΒΕ͍ͯͨɻ
  5. MVP࠾༻ϓϩδΣΫτ ͋Δ͋Δ ݸਓతͳܦݧʹΑΔ

  6. ςετ͕ॻ͔Ε͍ͯͳ͍

  7. Presenter͕Activity΍FragmentΛ ௚઀ࢀর͍ͯ͠Δ

  8. ViewʹϩδοΫ͕ॻ͔Ε͍ͯͨΓ Presenter͕ViewΛ௚઀ ॻ͖׵͍͑ͯͨΓ͢Δ

  9. PresenterͱViewͷ ੹຿෼่͚͕յ͍ͯ͠Δ…

  10. ͜ΕɺΫϥε͕෼྾ͯ͠ ίʔυ͕௥͍ʹ͘͘ͳͬͨ ͚ͩ͡ΌͶ…ʁ

  11. Ͳ͏ͯ͜͠Μͳ͜ͱʹ…

  12. ݪҼ ViewͱPresenterͷ੹຿෼͚͕ ग़དྷ͍ͯͳ͍

  13. Ͳ͏͢Ε͹͍͍͔

  14. ViewͱPresenterΛ෼཭͢Δ • Activity΍FragmentΛ௚઀Presenter͕ࢀরग़ དྷͯ͠·͏ͱɺ݁ہ੹຿෼่͚͕յ͢Δ

  15. ViewͱPresenterΛ෼཭͢Δ • Activity΍FragmentΛ௚઀Presenter͕ࢀরग़ དྷͯ͠·͏ͱɺ݁ہ੹຿෼่͚͕յ͢Δ • PresenterʹViewૢ࡞΍σʔλΞΫηε͕௚઀ ॻ͔Ε͍ͯΔͱϞοΫԽ͕ग़དྷͣPresenterͷ ςετΛॻ͘ͷ͸೉͘͠ͳΔ

  16. ViewͱPresenterΛ෼཭͢Δ • Activity΍FragmentΛ௚઀Presenter͕ࢀরग़ དྷͯ͠·͏ͱɺ݁ہ੹຿෼่͚͕յ͢Δ • PresenterʹViewૢ࡞΍σʔλΞΫηε͕௚઀ ॻ͔Ε͍ͯΔͱϞοΫԽ͕ग़དྷͣPresenterͷ ςετΛॻ͘ͷ͸೉͘͠ͳΔ • ·ͣ͸ViewΛந৅Խ͠Α͏

  17. αϯϓϧίʔυ

  18. ·ͣ͸Ξϯνύλʔϯ

  19. 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);
 }
 }
 }
  20. @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ଆͰϩδοΫΛ ͍࣋ͬͯΔ
  21. 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 -> {
 });
 }
 }
  22. 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͕ ௚઀ૢ࡞͍ͯ͠Δ
  23. ϦϑΝΫλޙ

  24. 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);
 }
 }
 }
  25. interface MainView {
 
 void startSubActivity();
 void showButton();
 void hideButton();


    void setButtonText(String s);
 
 class MainActivity extends AppCompatActivity implements MainView {
 "DUJWJUZͷૢ࡞Λ *OUFSGBDFͱͯ͠ந৅Խ "DUJWJUZ͸7JFXΛ࣮૷
  26. 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);
 } 7JFX͸1SFTFOUFSʹରͯ͠ ϥΠϑαΠΫϧͳͲͷ ΠϕϯτΛ౉͚ͩ͢ ϩδοΫ͸࣋ͨͣɺ ୹͍6*ૢ࡞Λ࣮ߦ͢Δ͚ͩͷ ϝιου܈Λ࣋ͭ
  27. 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 -> {
 });
 }
 }
  28. 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ʹ౉͢
  29. Presenter΁ͷςετͷॻ͖ํ

  30. Presenter΁ͷςετͷॻ͖ํ • MockitoͰView΍UseCaseΛϞοΫԽ • खಈͰPresenterͷϝιουΛݺͼϥΠϑαΠ Ϋϧ΍ΠϕϯτΛ࠶ݱ͢ΔࣄͰը໘શମͷྲྀ ΕΛςετग़དྷΔ • Presenterͷॲཧͷ݁ՌɺView΍UseCaseͷϝ ιου͕ظ଴௨Γʹݺ͹Ε͍ͯΔ͔ΛΞ΢τ

    ϓοτͱͯ͠νΣοΫ͢Δ
  31. @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();
 }
 }
  32. @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
  33. @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();
 }
 }
  34. @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Ͱ7JFX΍6TF$BTFͷ ϝιου͕ظ଴௨Γʹ ݺ͹Ε͍ͯΔ͔Λςετ͢Δ .PDL͕ฦ͢஋Λม͑ɺ݁Ռݺ ͹ΕΔϝιου΋มΘ͍ͬͯΔ ͔Λςετ͢Δ ΫϦοΫΠϕϯτΛखಈͰ࠶ݱ ͠ɺ݁ՌΛςετ͢Δ
  35. ࣮ࡍͷۀ຿΁ͷಋೖࣄྫ

  36. ܦҢ • ݩʑ͸Ξϯνύλʔϯʹ͍ۙઃܭͩͬͨ • IssueΛཱͯͯઃܭվળΛఏҊ • νʔϜ಺ͰϨϏϡʔ͠ͳ͕Β৽ઃܭΛࡦఆ • ࣮૷ྫͱͯ͠γϯϓϧͳը໘ΛҰͭϦϑΝΫ λʴςετίʔυΛ࡞੒͠ɺೝࣝΛ߹Θͤͨ

  37. ϨΠϠʔߏ੒ View (UIΞΫηε) ↑↓ Presenter (ViewϩδοΫɾڮ౉͠) ↓ UseCase (ϏδωεϩδοΫɾσʔλϨΠϠʔ΁ͷΞΫηε) ↓

    Repository (APIΞΫηε)
  38. ٕज़ελοΫ ɾKotlin1.1 ɾDagger2 ɾRxJava2 ɾRetrofit ɾMockito2 ɾRobolectric

  39. ӡ༻ • PRͷग़͍ͯΔϒϥϯνʹ͸CircleCIͰςετ͕૸Δ Α͏ʹઃఆ • RobolectricΛ࢖͍JUnitTestͱ࣮ͯ͠ߦ • طଘը໘͸ॱ࣍ϦϑΝΫλɺ৽نը໘͸ݪଇ৽ઃ ܭʴPresenterςετ͋ΓͰ࣮૷ •

    ϞσϧΫϥεͳͲʹ΋ੵۃతʹςετΛॻ͍͍ͯ͘
  40. ͦͷςετɺ໾ʹཱͬͯΔͷʁ

  41. ໾ʹཱ͍ͬͯ·͢ʂʂʂ

  42. ޮՌ • ·ͣ͸Presenterͷςετ͚ͩͰ΋͋Δͱը໘ શମ͕ςετग़དྷΔͷͰɺσάϨ๷ࢭͱͯ͠ ҆৺ײ͕͋Δ • Presenter͕fatʹͳ͍ͬͯͯ΋ɺςετ͕ॻ͔ Ε͍ͯΔͷͰϩδοΫΛϞσϧʹ੾Γग़ͨ͠ ΓɺϦϑΝΫλ͕΍Γ΍͘͢ͳͬͨ •

    ࣮ࡍʹPresenterͷςετʹΑͬͯσάϨ͕ݕ ग़͞Εॿ͚ΒΕͨࣄ͕Կ౓΋͋Δ
  43. σϝϦοτ • ͖ͪΜͱͨ͠MVPͰ࣮૷ʴςετΛॻ͜͏ͱ ͢Δͱɺ࣮૷޻਺͸΍ͬͺΓ૿͑Δ • ίʔυΛमਖ਼ͨ࣌͠΍࢓༷͕มΘͬͨ࣌ʹς ετͷमਖ਼͕μϧ͍ࣄ΋͋Δ • ͨͩ͠มߋΛҙࣝ͢Δͱ͍͏ҙຯͰ΋ςετ ίʔυͷमਖ਼͸༗ҙٛͰ͸͋Δ

  44. Presenter͔ΒAndroidͷ ςετΛ͸͡ΊΑ͏ʂ

  45. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠