testable android apps using data binding and mvvm

58
Testable Android Apps using data binding and MVVM Fabio Collini

Upload: fabio-collini

Post on 16-Apr-2017

1.959 views

Category:

Software


2 download

TRANSCRIPT

Page 1: Testable Android Apps using data binding and MVVM

Testable Android Apps using data binding and MVVM

Fabio Collini

Page 2: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 2

Ego slide@fabioCollini linkedin.com/in/fabiocollini Folder Organizer cosenonjaviste.it

nana bianca Freapp instal.com Rain tomorrow?

Page 3: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 3

Agenda

1. ROI and Legacy code 2. Model View ViewModel 3. JVM Unit tests 4. Mockito 5. Espresso

Page 4: Testable Android Apps using data binding and MVVM

GDG DevFest - Milano - October 2015 - @fabioCollini 4

1ROI and legacy code

Page 5: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 5

Quick survey

Do you write automated tests?

Page 6: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 6

Page 7: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 7

Return of Investment - ROI

Net profit Investment

Page 8: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 8

Legacy code

Edit and pray Vs

Cover and modify

Legacy code is code without unit tests

Page 9: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 9

Test After Development

Write the feature implementation Do some manual testing Try to write automatic tests Modify the initial implementation to test it

“Standard” Android code is not testable :(

Page 10: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 10

Legacy code dilemma

When we change code, we should have tests in place.

To put tests in place, we often have to change code.

Michael Feathers

Page 11: Testable Android Apps using data binding and MVVM

GDG DevFest - Milano - October 2015 - @fabioCollini 11

2Model View ViewModel

Page 12: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 12

Testable code

Data binding and MVVM

Page 13: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 13

Model View ViewModel

View

ViewModel

Model

DataBinding

Page 14: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 14

Android Model View ViewModel

View

ViewModel

Model

DataBinding

Retained on configuration change

Saved in Activity or Fragment state

Activity or Fragment

Page 15: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 15

mv2m

https://github.com/fabioCollini/mv2m

Page 16: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 16

NoteActivity

NoteViewModel

NoteModel

note_detail.xml NoteDetailBinding

DataBinding

Page 17: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 17

View ViewModel RetrofitService

onClick

updatebinding

Model

View ViewModel RetrofitServiceModel

request

response

binding

Page 18: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 18

NoteModel

Saved on Activity state

public class NoteModel implements Parcelable { private long noteId; private ObservableBoolean error = new ObservableBoolean(); private ObservableString title = new ObservableString(); private ObservableString text = new ObservableString(); private ObservableInt titleError = new ObservableInt(); private ObservableInt textError = new ObservableInt(); //...}

Page 19: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 19

NoteActivity

public class NoteActivity extends ViewModelActivity<NoteViewModel> { @Override public NoteViewModel createViewModel() { return new NoteViewModel(/* .. */); } @Override protected void onCreate(Bundle state) { super.onCreate(state); NoteDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.note_detail); binding.setViewModel(viewModel); } }

Page 20: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 20

note_detail.xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewModel" type="it.cosenonjaviste.core.NoteViewModel"/> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- ... --> </FrameLayout> </layout>

Page 21: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 21

note_detail.xml<LinearLayout app:visible="@{viewModel.loading}">

<ProgressBar />

<TextView /></LinearLayout> <LinearLayout app:visible=“@{viewModel.model.error}"> <TextView android:text="@string/error_loading_note"/> <Button android:text=“@string/retry"/></LinearLayout> <ScrollView app:visible="@{!viewModel.loading &amp;&amp; !viewModel.model.error}"> <!-- ... --></ScrollView>

Page 22: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 22

note_detail.xml<LinearLayout> <android.support.design.widget.TextInputLayout app:error=“@{viewModel.model.titleError}"> <EditText app:binding=“@{viewModel.model.title}" /> </android.support.design.widget.TextInputLayout>

<!-- ... --> <RelativeLayout> <Button android:enabled="@{!viewModel.sending}" app:onClick="@{viewModel.save}"/>

<ProgressBar app:visible=“@{viewModel.sending}" />

</RelativeLayout> </LinearLayout>

Page 23: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 23

app:binding

@BindingAdapter({"app:binding"})public static void bindEditText(EditText view, final ObservableString observableString) { if (view.getTag(R.id.binded) == null) { view.setTag(R.id.binded, true); view.addTextChangedListener(new TextWatcherAdapter() { @Override public void onTextChanged( CharSequence s, int st, int b, int c) { observableString.set(s.toString()); } }); } String newValue = observableString.get(); if (!view.getText().toString().equals(newValue)) { view.setText(newValue); }}

Page 24: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 24

app:visible app:onClick

@BindingAdapter({"app:visible"})public static void bindVisible(View view, boolean b) { view.setVisibility(b ? View.VISIBLE : View.INVISIBLE); } @BindingAdapter({"app:onClick"})public static void bindOnClick(View view, final Runnable listener) { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.run(); } });}

Page 25: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 25

NoteViewModelpublic class NoteViewModel extends ViewModel<NoteModel> { //... @Override public NoteModel createDefaultModel() { return new NoteModel(); } @Override public void resume() { if (!getModel().isLoaded()) { reloadData(); } } public void reloadData() { } //...}

Page 26: Testable Android Apps using data binding and MVVM

GDG DevFest - Milano - October 2015 - @fabioCollini 26

3JVM Unit tests

Page 27: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 27

Instrumentation tests run on a device (real or emulated)

high code coverage

Vs JVM tests

fast low code coverage

Page 28: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini

JVM Test

28

NoteActivity

NoteViewModel

NoteModel

note_detail.xml NoteDetailBinding

DataBinding

Page 29: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 29

NoteViewModel.reloadDatapublic class NoteViewModel extends ViewModel<NoteModel> { //...

@Override public void resume() { if (!getModel().isLoaded()) { reloadData(); } } public void reloadData() { try { Note note = NoteLoader.singleton().load(); getModel().update(note); } catch (Exception e) { getModel().getError().set(true); } } //...}

Page 30: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 30

First test

AssertJ

@Testpublic void testLoadData() { NoteViewModel viewModel = new NoteViewModel(); NoteModel model = viewModel.initAndResume(); assertThat(model.getTitle().get()).isEqualTo("???"); assertThat(model.getText().get()).isEqualTo("???"); assertThat(model.getError().get()).isFalse(); }

Page 31: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 31

NoteLoader.singletonpublic class NoteViewModel extends ViewModel<NoteModel> { //...

@Override public void resume() { if (!getModel().isLoaded()) { reloadData(); } } public void reloadData() { try { Note note = NoteLoader.singleton().load(); getModel().update(note); } catch (Exception e) { getModel().getError().set(true); } } //...}

Page 32: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 32

Dependency Injectionpublic class NoteViewModel extends ViewModel<NoteModel, NoteView> {

private NoteLoader noteLoader;

public NoteViewModel(NoteLoader noteLoader) { this.noteLoader = noteLoader; } public void reloadData() { try { Note note = noteLoader.load(); getModel().update(note); } catch (Exception e) { getModel().getError().set(true); } } //... }

Page 33: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 33

NoteLoaderStub

public class NoteLoaderStub implements NoteLoader { private Note note; public NoteLoaderStub(Note note) { this.note = note; } @Override public Note load() { return note; } }

Page 34: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 34

Test with stub

@Testpublic void testLoadData() { NoteLoaderStub stub = new NoteLoaderStub(new Note(1, "a", "b")); NoteViewModel viewModel = new NoteViewModel(stub); NoteModel model = viewModel.initAndResume(); assertThat(model.getTitle().get()).isEqualTo("a"); assertThat(model.getText().get()).isEqualTo("b"); assertThat(model.getError().get()).isFalse(); }

Page 35: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini

public void save() { NoteModel model = getModel(); boolean titleValid = checkMandatory( model.getTitle(), model.getTitleError()); boolean textValid = checkMandatory( model.getText(), model.getTextError()); if (titleValid && textValid) { try { noteSaver.save( model.getNoteId(), model.getTitle().get(), model.getText().get());

messageManager.showMessage(R.string.note_saved); } catch (RetrofitError e) { messageManager.showMessage( R.string.error_saving_note); } }}

35

save method

Dependency Injection

Dependency Injection

Page 36: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 36

SnackbarMessageManager

public class SnackbarMessageManager implements MessageManager { private Activity activity; @Override public void showMessage(int message) { if (activity != null) { Snackbar.make( activity.findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG ).show(); } } @Override public void setActivity(Activity activity) { this.activity = activity; }}

Page 37: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 37

MessageManagerSpy

public class MessageManagerSpy implements MessageManager { public int message; @Override public void showMessage(int message) { this.message = message; } @Override public void setActivity(Activity activity) { }}

Page 38: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 38

NoteSaverSpy

public class NoteSaverSpy implements NoteSaver { public long id; public String title; public String text; @Override public Response save( long id, String title, String text) { this.id = id; this.title = title; this.text = text; return null; } }

Page 39: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 39

Test with spy@Testpublic void testSaveData() { NoteLoaderStub stub = new NoteLoaderStub(new Note(1, "a", "b")); NoteSaverSpy saverSpy = new NoteSaverSpy(); MessageManagerSpy messageSpy = new MessageManagerSpy(); NoteViewModel viewModel = new NoteViewModel( stub, saverSpy, messageSpy); NoteModel model = viewModel.initAndResume(); model.getTitle().set("newTitle"); model.getText().set("newText"); viewModel.save(); assertThat(saverSpy.id).isEqualTo(1L); assertThat(saverSpy.title).isEqualTo("newTitle"); assertThat(saverSpy.text).isEqualTo("newText"); assertThat(messageSpy.message) .isEqualTo(R.string.note_saved); }

Page 40: Testable Android Apps using data binding and MVVM

GDG DevFest - Milano - October 2015 - @fabioCollini 40

4Mockito

Page 41: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 41

Mockito@Testpublic void testLoadData() { NoteLoader noteLoader = Mockito.mock(NoteLoader.class); NoteSaver noteSaver = Mockito.mock(NoteSaver.class); MessageManager messageManager = Mockito.mock(MessageManager.class); NoteViewModel viewModel = new NoteViewModel( noteLoader, noteSaver, messageManager); when(noteLoader.load()) .thenReturn(new Note(123, "title", "text"));

NoteModel model = viewModel.initAndResume(); assertThat(model.getTitle().get()).isEqualTo("title"); assertThat(model.getText().get()).isEqualTo("text"); }

Page 42: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini

MockLoader

MockLoaderNoteLoader

NoteLoader

42

ViewModel

initAndResume

update

Model

request

response

JVM Test

ViewModel ModelJVM Test

assert

when().thenReturn()

Page 43: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 43

Mockito@Testpublic void testSaveData() { //... NoteModel model = viewModel.initAndResume(); model.getTitle().set("newTitle"); model.getText().set("newText"); viewModel.save(); verify(noteSaver) .save(eq(123L), eq("newTitle"), eq("newText")); verify(messageManager) .showMessage(eq(R.string.note_saved)); }

Page 44: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini

MockMessage ManagerMessage Manager

MockMessage ManagerMessage Manager

44

ViewModel MockSaver

save

showMessage

Model

request

response

JVM Test

ViewModel MockSaverModelJVM Test

verify

verify

NoteSaver

NoteSaver

Page 45: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 45

SetUp method public class NoteViewModelTest { private NoteLoader noteLoader; private NoteSaver noteSaver; private MessageManager messageManager; private NoteViewModel viewModel; @Before public void setUp() { noteLoader = Mockito.mock(NoteLoader.class); noteSaver = Mockito.mock(NoteSaver.class); messageManager = Mockito.mock(MessageManager.class); viewModel = new NoteViewModel( noteLoader, noteSaver, messageManager);

when(noteLoader.load()) .thenReturn(new Note(123, "title", "text")); } //...}

Page 46: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 46

@Mock and @InjectMocks@RunWith(MockitoJUnitRunner.class) public class NoteViewModelTest { @Mock NoteLoader noteLoader; @Mock NoteSaver noteSaver; @Mock MessageManager messageManager; @InjectMocks NoteViewModel viewModel; @Before public void setUp() throws Exception { when(noteLoader.load()) .thenReturn(new Note(123, "title", "text")); } //...}

Page 47: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 47

Dagger

A fast dependency injector for Android and Java v1 developed at Square https://github.com/square/dagger

v2 developed at Google https://github.com/google/dagger

Configuration using annotations and Java classes Based on annotation processing (no reflection)

Page 48: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 48

Background executor

if (titleValid && textValid) { sending.set(true); backgroundExecutor.execute(new Runnable() { @Override public void run() { try { noteSaver.save(getModel().getNoteId(), getModel().getTitle().get(), getModel().getText().get()); hideSendProgressAndShowMessage( R.string.note_saved); } catch (RetrofitError e) { hideSendProgressAndShowMessage( R.string.error_saving_note); } } }); }

Page 49: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 49

Ui Executor

private void hideSendProgressAndShowMessage(final int msg) { uiExecutor.execute(new Runnable() { @Override public void run() { messageManager.showMessage(msg); sending.set(false); } }); }

Page 50: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 50

Test using single thread@RunWith(MockitoJUnitRunner.class) public class NoteViewModelTest { @Mock NoteView view; @Mock NoteLoader noteLoader; @Mock NoteSaver noteSaver; @Spy Executor executor = new Executor() { @Override public void execute(Runnable command) { command.run(); } }; @InjectMocks NoteViewModel viewModel; //...}

Page 51: Testable Android Apps using data binding and MVVM

GDG DevFest - Milano - October 2015 - @fabioCollini 51

5Espresso

Page 52: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 52

NoteLoaderpublic class NoteLoader { private static NoteLoader instance; public static NoteLoader singleton() { if (instance == null) { instance = new NoteLoader(); } return instance; } private NoteLoader() { } @VisibleForTesting public static void setInstance(NoteLoader instance) { NoteLoader.instance = instance; } //...}

Page 53: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini

public class NoteActivityTest { @Rule public ActivityTestRule<NoteActivity> rule = new ActivityTestRule<>(NoteActivity.class, false, false); private NoteLoader noteLoader; @Before public void setUp() throws Exception { noteLoader = Mockito.mock(NoteLoader.class); NoteLoader.setInstance(noteLoader); }

//...}

53

NoteActivityTest

Page 54: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 54

Reload test@Testpublic void testReloadAfterError() { when(noteLoader.load()) .thenThrow( RetrofitError.networkError("url", new IOException())) .thenReturn(new Note(123, "aaa", "bbb")); rule.launchActivity(null); onView(withText(R.string.retry)).perform(click()); onView(withText(“aaa")) .check(matches(isDisplayed())); onView(withText(“bbb")) .check(matches(isDisplayed())); }

Page 55: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 55

View ViewModel MockLoader

perform(click())

updatebinding

Model

requestresponse

EspressoTest

View ViewModel MockLoaderModelEspressoTest

onView

verify

NoteLoader

NoteLoader

when().thenReturn()

onClickbinding

Page 56: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 56

Android Model View ViewModel

Activity (or Fragment) is the View All the business logic is in the ViewModel ViewModel is managed using Dependency Injection Model is the Activity (or Fragment) state ViewModel is retained on configuration change ViewModel is testable using a JVM test

Page 57: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 57

Links

mockito.org joel-costigliola.github.io/assertj

Jay Fields - Working Effectively with Unit Tests Michael Feathers - Working Effectively with Legacy Code

medium.com/@fabioCollini/android-data-binding-f9f9d3afc761

github.com/fabioCollini/mv2m github.com/commit-non-javisti/CoseNonJavisteAndroidApp

Page 58: Testable Android Apps using data binding and MVVM

GDG DevFest – Milano – October 2015 – @fabioCollini 58

Thanks for your attention!

androidavanzato.it

Questions?