Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

MVP΍ͬͯ·͔͢ʁ • MVP = Model View Presenter • Androidք۾ͰMVP͕࿩୊ʹͳͬͨͷ͸2೥͘Β͍લɻ • MVPΛ࠾༻͍ͯ͠ΔϓϩδΣΫτ΋݁ߏݟ͔͚Δҹ৅ɻ • ओͳϝϦοτͱͯ͠Α͘ςετͷॻ͖΍͕͢͞ ڍ͛ΒΕ͍ͯͨɻ

Slide 5

Slide 5 text

MVP࠾༻ϓϩδΣΫτ ͋Δ͋Δ ݸਓతͳܦݧʹΑΔ

Slide 6

Slide 6 text

ςετ͕ॻ͔Ε͍ͯͳ͍

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Ͳ͏ͯ͜͠Μͳ͜ͱʹ…

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Ͳ͏͢Ε͹͍͍͔

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

αϯϓϧίʔυ

Slide 18

Slide 18 text

·ͣ͸Ξϯνύλʔϯ

Slide 19

Slide 19 text

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);
 }
 }
 }

Slide 20

Slide 20 text

@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ଆͰϩδοΫΛ ͍࣋ͬͯΔ

Slide 21

Slide 21 text

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 -> {
 });
 }
 }

Slide 22

Slide 22 text

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͕ ௚઀ૢ࡞͍ͯ͠Δ

Slide 23

Slide 23 text

ϦϑΝΫλޙ

Slide 24

Slide 24 text

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);
 }
 }
 }

Slide 25

Slide 25 text

interface MainView {
 
 void startSubActivity();
 void showButton();
 void hideButton();
 void setButtonText(String s);
 
 class MainActivity extends AppCompatActivity implements MainView {
 "DUJWJUZͷૢ࡞Λ *OUFSGBDFͱͯ͠ந৅Խ "DUJWJUZ͸7JFXΛ࣮૷

Slide 26

Slide 26 text

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*ૢ࡞Λ࣮ߦ͢Δ͚ͩͷ ϝιου܈Λ࣋ͭ

Slide 27

Slide 27 text

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 -> {
 });
 }
 }

Slide 28

Slide 28 text

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ʹ౉͢

Slide 29

Slide 29 text

Presenter΁ͷςετͷॻ͖ํ

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

@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();
 }
 }

Slide 32

Slide 32 text

@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

Slide 33

Slide 33 text

@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();
 }
 }

Slide 34

Slide 34 text

@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͕ฦ͢஋Λม͑ɺ݁Ռݺ ͹ΕΔϝιου΋มΘ͍ͬͯΔ ͔Λςετ͢Δ ΫϦοΫΠϕϯτΛखಈͰ࠶ݱ ͠ɺ݁ՌΛςετ͢Δ

Slide 35

Slide 35 text

࣮ࡍͷۀ຿΁ͷಋೖࣄྫ

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

ٕज़ελοΫ ɾKotlin1.1 ɾDagger2 ɾRxJava2 ɾRetrofit ɾMockito2 ɾRobolectric

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

ͦͷςετɺ໾ʹཱͬͯΔͷʁ

Slide 41

Slide 41 text

໾ʹཱ͍ͬͯ·͢ʂʂʂ

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

σϝϦοτ • ͖ͪΜͱͨ͠MVPͰ࣮૷ʴςετΛॻ͜͏ͱ ͢Δͱɺ࣮૷޻਺͸΍ͬͺΓ૿͑Δ • ίʔυΛमਖ਼ͨ࣌͠΍࢓༷͕มΘͬͨ࣌ʹς ετͷमਖ਼͕μϧ͍ࣄ΋͋Δ • ͨͩ͠มߋΛҙࣝ͢Δͱ͍͏ҙຯͰ΋ςετ ίʔυͷमਖ਼͸༗ҙٛͰ͸͋Δ

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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