Testable Android Apps DroidCon Italy 2015

61
Testable Android Apps Fabio Collini

Transcript of Testable Android Apps DroidCon Italy 2015

Page 1: Testable Android Apps DroidCon Italy 2015

Testable Android AppsFabio Collini

Page 2: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 2

Fabio Collini@fabioCollini linkedin.com/in/fabiocollini Folder Organizer cosenonjaviste.it

nana bianca Freapp instal.com Rain tomorrow?

Page 3: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 3

Quick survey

Do you write automated tests?

Page 4: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 4

Return of Investment - ROI

Net profit Investment

Page 5: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 5

Agenda

1. Legacy code 2. Dependency Injection 3. Mockito 4. Dagger 5. Dagger & Android 6. Model View Presenter

Page 6: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 6

TestableAndroidAppsDroidCon15

https://github.com/fabioCollini/TestableAndroidAppsDroidCon15

Page 7: Testable Android Apps DroidCon Italy 2015

DroidCon Italy - Torino - April 2015 - @fabioCollini

1Legacy code

Page 8: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 8

Legacy code

Edit and pray Vs

Cover and modify

Legacy code is code without unit tests

Page 9: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 9

Instrumentation tests run on a device (real or emulated)

high code coverage

Vs JVM tests

fast low code coverage

Page 10: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 10

Espressopublic class PostListActivityTest {

//see https://gist.github.com/JakeWharton/1c2f2cadab2ddd97f9fb @Rule public ActivityRule<PostListActivity> rule = new ActivityRule<>(PostListActivity.class);

}

@Test public void showListActivity() { onView(withText("???")) .check(matches(isDisplayed())); } @Test public void showErrorLayoutOnServerError() { //??? onView(withId(R.id.error_layout)) .check(matches(isDisplayed())); }

Page 11: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 11

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 12: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 12

TestablePostListActivity

public class TestablePostListActivity extends PostListActivity { public static Observable<List<Post>> result; @Override protected Observable<List<Post>> createListObservable() { return result; }}

Page 13: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 13

PostListActivityTestpublic class PostListActivityTest { @Rule public ActivityRule<TestablePostListActivity> rule = new ActivityRule<>( TestablePostListActivity.class, false ); @Test public void showListActivity() { TestablePostListActivity.result = Observable.just( createPost(1), createPost(2), createPost(3) ).toList(); rule.launchActivity(); onView(withText("title 1”)) .check(matches(isDisplayed())); }

Page 14: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 14

PostListActivityTest

@Test public void checkErrorLayoutDisplayed() { TestablePostListActivity.result = Observable.error(new IOException()); rule.launchActivity(); onView(withId(R.id.error_layout)) .check(matches(isDisplayed()));}

Page 15: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 15

Legacy code

Not the perfect solution First step to increase coverage Then modify and refactor

Page 16: Testable Android Apps DroidCon Italy 2015

DroidCon Italy - Torino - April 2015 - @fabioCollini

2Dependency Injection

Page 17: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 17

PostBatch

public void execute() { PostResponse postResponse = createService().listPosts(); EmailSender emailSender = new EmailSender(); List<Post> posts = postResponse.getPosts(); for (Post post : posts) { emailSender.sendEmail(post); }} private static WordPressService createService() { //...}

Page 18: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 18

PostBatch

WordPressService

EmailSender

Class under test

Collaborator

Collaborator

Page 19: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 19

PostBatchTest

public class PostBatchTest { private PostBatch postBatch = new PostBatch(); @Test public void testExecute() { postBatch.execute(); //??? } }

Page 20: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 20

Inversion Of Controlprivate WordPressService wordPressService; private EmailSender emailSender; public PostBatch(WordPressService wordPressService, EmailSender emailSender) { this.wordPressService = wordPressService; this.emailSender = emailSender;} public void execute() { PostResponse postResponse = wordPressService.listPosts(); List<Post> posts = postResponse.getPosts(); for (Post post : posts) { emailSender.sendEmail(post); } }

Page 21: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 21

Dependency Injection

public class Main { public static void main(String[] args) { new PostBatch( createService(), new EmailSender() ).execute(); } private static WordPressService createService() { //... } }

Page 22: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 22

WordPressServiceStub

public class WordPressServiceStub implements WordPressService { private PostResponse postResponse; public WordPressServiceStub(PostResponse postResponse) { this.postResponse = postResponse; } @Override public PostResponse listPosts() { return postResponse; }}

Page 23: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 23

EmailSenderSpy

public class EmailSenderSpy extends EmailSender { private int emailCount; @Override public void sendEmail(Post p) { emailCount++; } public int getEmailCount() { return emailCount; }}

Page 24: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 24

PostBatch

WordPressService

EmailSender

Stub

Spy

Page 25: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 25

Test doublesprivate PostBatch postBatch; private EmailSenderSpy emailSenderSpy; private WordPressServiceStub serviceStub;

@Testpublic void testExecute() { postBatch.execute(); assertEquals(3, emailSenderSpy.getEmailCount());}

@Before public void init() { emailSenderSpy = new EmailSenderSpy(); serviceStub = new WordPressServiceStub( new PostResponse(new Post(), new Post(), new Post()) ); postBatch = new PostBatch(serviceStub, emailSenderSpy); }

Page 26: Testable Android Apps DroidCon Italy 2015

DroidCon Italy - Torino - April 2015 - @fabioCollini

3Mockito

Page 27: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 27

Mockitoprivate WordPressService service; private EmailSender emailSender; private PostBatch postBatch;

@Before public void init() { emailSender = Mockito.mock(EmailSender.class); service = Mockito.mock(WordPressService.class); postBatch = new PostBatch(service, emailSender); }

@Test public void testExecute() { when(service.listPosts()).thenReturn( new PostResponse(new Post(), new Post(), new Post())); postBatch.execute(); verify(emailSender, times(3)).sendEmail(any(Post.class));}

Arrange Act Assert

Page 28: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 28

@InjectMocks@RunWith(MockitoJUnitRunner.class) public class PostBatchTest { @Mock WordPressService service; @Mock EmailSender sender; @InjectMocks PostBatch postBatch; @Test public void testExecute() { when(service.listPosts()).thenReturn( new PostResponse(new Post(), new Post(), new Post())); postBatch.execute(); verify(sender, times(3)).sendEmail(any(Post.class)); }}

Page 29: Testable Android Apps DroidCon Italy 2015

DroidCon Italy - Torino - April 2015 - @fabioCollini

4Dagger

Page 30: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 30

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 31: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 31

Module@Modulepublic class MainModule { @Provides @Singleton EmailSender provideEmailSender() { return new EmailSender(); } @Provides @Singleton WordPressService provideService() { //... } @Provides PostBatch providePostsBatch( WordPressService wordPressService, EmailSender emailSender) { return new PostBatch(wordPressService, emailSender); }}

Page 32: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 32

PostBatch

WordPressService

EmailSenderCo

mpo

nent

Main

Page 33: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 33

Component

@Singleton@Component(modules = MainModule.class) public interface MainComponent { PostBatch getBatch();}

public class Main { public static void main(String[] args) { MainComponent component = DaggerMainComponent.create(); PostBatch batch = component.getBatch(); batch.execute(); }}

Page 34: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 34

Inject annotationpublic class PostBatch { private WordPressService wordPressService; private EmailSender emailSender; @Inject public PostBatch( WordPressService wordPressService, EmailSender emailSender) { this.wordPressService = wordPressService; this.emailSender = emailSender; } }

public class PostBatch { @Inject WordPressService wordPressService; @Inject EmailSender emailSender; @Inject public PostBatch() { }}

Page 35: Testable Android Apps DroidCon Italy 2015

DroidCon Italy - Torino - April 2015 - @fabioCollini

5Dagger & Android

Page 36: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 36

PostListActivity

WordPressService

ShareActivity

ShareExecutor

Page 37: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 37

ShareExecutorpublic class ShareExecutor { private Context context; public ShareExecutor(Context context) { this.context = context; } public void startSendActivity(String title, String body) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TITLE, title); intent.putExtra(Intent.EXTRA_TEXT, body); intent.setType("text/plain"); Intent chooserIntent = Intent.createChooser(intent, context.getResources().getText(R.string.share)); chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(chooserIntent); }}

Page 38: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 38

ApplicationModule

@Module public class ApplicationModule { private Application application;

public ApplicationModule(Application application) { this.application = application; } @Provides @Singleton WordPressService providesService() { //... } @Provides @Singleton ShareExecutor shareExecutor() { return new ShareExecutor(application); }}

Page 39: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 39

ApplicationComponent

@Singleton @Component(modules = ApplicationModule.class) public interface ApplicationComponent { void inject(PostListActivity activity);

void inject(ShareActivity activity);

}

Page 40: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 40

Component

PostListActivity

Application

ShareActivity

ShareExecutorWordPressService

Page 41: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 41

Applicationpublic class CnjApplication extends Application { private ApplicationComponent component; @Override public void onCreate() { super.onCreate(); component = DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(this)) .build(); } public ApplicationComponent getComponent() { return component; } public void setComponent(ApplicationComponent c) { this.component = c; }}

Page 42: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 42

PostListActivitypublic class PostListActivity extends ActionBarActivity { //... @Inject WordPressService wordPressService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

CnjApplication app = (CnjApplication) getApplicationContext(); ApplicationComponent component = app.getComponent(); component.inject(this); //... } //...}

Page 43: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 43

Component

PostListActivity

Application

ShareExecutorWordPressService

TestComponent

MockMock

Test

Page 44: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 44

TestModule

@Modulepublic class TestModule { @Provides @Singleton ShareExecutor provideShareExecutor() { return Mockito.mock(ShareExecutor.class); } @Provides @Singleton WordPressService providesWordPressService() { return Mockito.mock(WordPressService.class); }}

Page 45: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 45

TestComponent

@Singleton@Component(modules = TestModule.class) public interface TestComponent extends ApplicationComponent { void inject(PostListActivityTest test); void inject(ShareActivityTest test);

}

Page 46: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 46

PostListActivityTestpublic class PostListActivityTest { @Inject WordPressService wordPressService; @Rule public ActivityRule<PostListActivity> rule = new ActivityRule<>(PostListActivity.class, false);

@Before public void setUp() { TestComponent component = DaggerTestComponent.create(); CnjApplication application = (CnjApplication) rule.getApplication(); application.setComponent(component); component.inject(this); }

//...

Page 47: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 47

PostListActivityTest

@Test public void showListActivity() { when(wordPressService.listPosts()).thenReturn( Observable.just(new PostResponse(createPost(1), createPost(2), createPost(3)))); rule.launchActivity(); onView(withText("title 1”)) .check(matches(isDisplayed()));}

Page 48: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 48

PostListActivityTest

@Test public void showErrorLayoutOnServerError() { when(wordPressService.listPosts()).thenReturn( Observable.error(new IOException("error!"))); rule.launchActivity(); onView(withId(R.id.error_layout)) .check(matches(isDisplayed()));}

Page 49: Testable Android Apps DroidCon Italy 2015

DroidCon Italy - Torino - April 2015 - @fabioCollini

6Model View Presenter

Page 50: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 50

Model View Presenter

View

Presenter

Model

Page 51: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 51

View Presenter RetrofitService

onClick

updateupdate(model)

Model

View Presenter RetrofitServiceModel

request

response

Page 52: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 52

View Presenter MockService

perform(click())

updateupdate(model)

Model

requestresponse

EspressoTest

View Presenter MockServiceModelEspressoTest

onView

verify

when().thenReturn()

onClick

Page 53: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 53

MockView Presenter MockService

onClick

updateupdate(model)

Model

requestresponse

JVM Test

MockView Presenter MockServiceModelJVM Test

verify

verify

assert

when().thenReturn()

Page 54: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 54

View Presenter

init

update(model)

ModelEspressoTest

View Presenter ModelEspressoTest

onView

create

Page 55: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 55

Android Model View Presenter

Activity (or Fragment) is the View All the business logic is in the Presenter Presenter is managed using Dagger Model is the Activity (or Fragment) state Presenter is retained on configuration change

Page 56: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 56

PostListPresenter JVM Test@RunWith(MockitoJUnitRunner.class) public class PostListPresenterTest { @Mock WordPressService wordPressService; @Mock PostListActivity view; private PostListPresenter postListPresenter; @Before public void setUp() { SchedulerManager schedulerManager = new SchedulerManager( Schedulers.immediate(), Schedulers.immediate()); postListPresenter = new PostListPresenter( wordPressService, schedulerManager ); } //...

Page 57: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 57

PostListPresenter JVM Test

@Testpublic void loadingPosts() { when(wordPressService.listPosts()).thenReturn( Observable.just(new PostResponse(new Post(), new Post(), new Post()))); PostListModel model = new PostListModel(); postListPresenter.setModel(model); postListPresenter.resume(view); assertEquals(3, model.getItems().size());}

Page 58: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 58

PostListPresenter JVM Test

@Testpublic void clickOnItem() { PostListModel model = new PostListModel(); model.setItems(Arrays.asList( createPost(1), createPost(2), createPost(3))); postListPresenter.setModel(model); postListPresenter.resume(view); postListPresenter.onItemClick(1); verify(view).startShareActivity( eq("title 2"), eq("name 2 last name 2\nexcerpt 2"));}

Page 59: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 59

SharePresenter JVM Test@RunWith(MockitoJUnitRunner.class) public class SharePresenterTest { @Mock ShareExecutor shareExecutor; @Mock ShareActivity view; @InjectMocks SharePresenter sharePresenter; @Test public void testValidationOk() { ShareModel model = new ShareModel(); sharePresenter.init(view, model); sharePresenter.share("title", "body"); verify(shareExecutor) .startSendActivity(eq("title"), eq("body")); }}

Page 60: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 60

TL;DR

Dagger Mockito Espresso UI tests JVM Presenter tests

Page 61: Testable Android Apps DroidCon Italy 2015

DroidCon Italy – Torino – April 2015 – @fabioCollini 61

Thanks for your attention!

Questions?

github.com/fabioCollini/TestableAndroidAppsDroidCon15

github.com/google/dagger mockito.org

cosenonjaviste.it/libri