Top Banner
88

Testable android apps

Apr 14, 2017

Download

Technology

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Testable android apps
Page 2: Testable android apps

Writing Testable Apps

Page 3: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 4: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 5: Testable android apps

Why tests?

Page 6: Testable android apps

Why tests?

Page 7: Testable android apps

Why are we here?

Page 8: Testable android apps

“the goal of software delivery is to sustainably minimize the lead time to business

impact”

Page 9: Testable android apps

Yes, but why tests?

Page 10: Testable android apps

–Steve Freeman and Nat Pryce, authors of Growing Object Oriented Software Guided by Tests

“for a class to be easy to unit-test, the class must…be loosely coupled and highly cohesive

—in other words, well-designed.”

Page 11: Testable android apps

“We invest in this huge testing framework…

engineers here have the power to try out an idea

and ship it to maybe 10,000 people or 100,000

people.”

Page 12: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 13: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 14: Testable android apps

–Michael Feathers, Working Effectively with Legacy Code

“One of the things that nearly everyone notices when they try to write tests for existing code is

just how poorly suited code is to testing.”

Page 15: Testable android apps
Page 16: Testable android apps
Page 17: Testable android apps
Page 18: Testable android apps

public class PresenterFragmentImpl extends Fragment implements Presenter, UpdatableView.UserActionListener, LoaderManager.LoaderCallbacks<Cursor> {

@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Loader<Cursor> cursorLoader = createLoader(id, args); mLoaderIdlingResource.onLoaderStarted(cursorLoader); return cursorLoader; }

@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { processData(loader, data); mLoaderIdlingResource.onLoaderFinished(loader); } }

Page 19: Testable android apps

public class PresenterFragmentImpl extends Fragment implements Presenter, UpdatableView.UserActionListener, LoaderManager.LoaderCallbacks<Cursor> {

@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Loader<Cursor> cursorLoader = createLoader(id, args); mLoaderIdlingResource.onLoaderStarted(cursorLoader); return cursorLoader; }

@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { processData(loader, data); mLoaderIdlingResource.onLoaderFinished(loader); } }

Page 20: Testable android apps

What makes code testable?

Page 21: Testable android apps
Page 22: Testable android apps

@Override public void onSharedPreferenceChanged(SharedPreferences sharedPrefs,

String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { Intent intent; if (SettingsUtils.shouldSyncCalendar(getActivity())) { // Add all calendar entries intent = new Intent(ACTION_UPDATE_ALL_SESSIONS_CALENDAR); } else { // Remove all calendar entries intent = new Intent(ACTION_CLEAR_ALL_SESSIONS_CALENDAR); }

intent.setClass(getActivity(), SessionCalendarService.class); getActivity().startService(intent); } }

Page 23: Testable android apps

@Override public void onSharedPreferenceChanged(SharedPreferences sharedPrefs,

String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { Intent intent; if (SettingsUtils.shouldSyncCalendar(getActivity())) { // Add all calendar entries intent = new Intent(ACTION_UPDATE_ALL_SESSIONS_CALENDAR); } else { // Remove all calendar entries intent = new Intent(ACTION_CLEAR_ALL_SESSIONS_CALENDAR); }

intent.setClass(getActivity(), SessionCalendarService.class); getActivity().startService(intent); } }

Page 24: Testable android apps

@Override public void onSharedPreferenceChanged(SharedPreferences sharedPrefs,

String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { Intent intent; if (SettingsUtils.shouldSyncCalendar(getActivity())) { // Add all calendar entries intent = new Intent(ACTION_UPDATE_ALL_SESSIONS_CALENDAR); } else { // Remove all calendar entries intent = new Intent(ACTION_CLEAR_ALL_SESSIONS_CALENDAR); }

intent.setClass(getActivity(), SessionCalendarService.class); getActivity().startService(intent); } }

Page 25: Testable android apps

@Test public void onSPChangedRemovesSessions() throws Exception { // Arrange

//Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR);

//Assert

}

Page 26: Testable android apps

@Test public void onSPChangedRemovesSessions() throws Exception { // Arrange

//Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR);

//Assert

}

Page 27: Testable android apps

@Override public void onSharedPreferenceChanged(SharedPreferences sharedPrefs,

String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { Intent intent; if (SettingsUtils.shouldSyncCalendar(getActivity())) { // Add all calendar entries intent = new Intent(ACTION_UPDATE_ALL_SESSIONS_CALENDAR); } else { // Remove all calendar entries intent = new Intent(ACTION_CLEAR_ALL_SESSIONS_CALENDAR); }

intent.setClass(getActivity(), SessionCalendarService.class); getActivity().startService(intent); } }

Page 28: Testable android apps

@Test public void onSPChangedRemovesSessions() throws Exception { // Arrange

//Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR);

//Assert

}

Page 29: Testable android apps

@Override public void onSharedPreferenceChanged(SharedPreferences sharedPrefs,

String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { Intent intent; if (SettingsUtils.shouldSyncCalendar(getActivity())) { // Add all calendar entries intent = new Intent(ACTION_UPDATE_ALL_SESSIONS_CALENDAR); } else { // Remove all calendar entries intent = new Intent(ACTION_CLEAR_ALL_SESSIONS_CALENDAR); }

intent.setClass(getActivity(), SessionCalendarService.class); getActivity().startService(intent); } }

Page 30: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 31: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 32: Testable android apps
Page 33: Testable android apps

–Michael Feathers, author of Working Effectively with Legacy Code

“A seam is a place where you can alter behavior in your program without editing in that

place.”

Page 34: Testable android apps

Without seams, it’s often difficult to arrange and/or

assert

Page 35: Testable android apps
Page 36: Testable android apps

class CalendarUpdatingOnSharedPreferenceChangedListener {

void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }

Page 37: Testable android apps

class CalendarUpdatingOnSharedPreferenceChangedListener {

void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }

Page 38: Testable android apps

class CalendarUpdatingOnSharedPreferenceChangedListener {

void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }

Page 39: Testable android apps

class CalendarUpdatingOnSharedPreferenceChangedListener {

void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }

Page 40: Testable android apps

@Test public void onPreferenceChangedClearedCalendar() throws Exception {

// Arrange CUOSPCListener listener = new CUOSPCListener(mSessionUpdateLauncher);

final CalendarPreferences calendarPreferences = mock(CalendarPreferences.class); when(calendarPreferences.shouldSyncCalendar()).thenReturn(false);

// Act listener.onPreferenceChanged(calendarPreferences, SettingsUtils.PREF_SYNC_CALENDAR);

// Assert verify(mSessionUpdateLauncher).launchClearAllSessionsUpdate(); }

Page 41: Testable android apps

@Test public void onPreferenceChangedClearedCalendar() throws Exception {

// Arrange CUOSPCListener listener = new CUOSPCListener(mSessionUpdateLauncher);

final CalendarPreferences calendarPreferences = mock(CalendarPreferences.class); when(calendarPreferences.shouldSyncCalendar()).thenReturn(false);

// Act listener.onPreferenceChanged(calendarPreferences, SettingsUtils.PREF_SYNC_CALENDAR);

// Assert verify(mSessionUpdateLauncher).launchClearAllSessionsUpdate(); }

Page 42: Testable android apps

@Test public void onPreferenceChangedClearedCalendar() throws Exception {

// Arrange CUOSPCListener listener = new CUOSPCListener(mSessionUpdateLauncher);

final CalendarPreferences calendarPreferences = mock(CalendarPreferences.class); when(calendarPreferences.shouldSyncCalendar()).thenReturn(false);

// Act listener.onPreferenceChanged(calendarPreferences, SettingsUtils.PREF_SYNC_CALENDAR);

// Assert verify(mSessionUpdateLauncher).launchClearAllSessionsUpdate(); }

Page 43: Testable android apps

class CalendarUpdatingOnSharedPreferenceChangedListener {

void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) {

if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }

Page 44: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 45: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives

you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 46: Testable android apps

Object Seams

Page 47: Testable android apps

–Michael Feathers

“The fundamental thing to recognize is that when we look at a call in an object-oriented

program, it does not define which method will actually be executed.”

Page 48: Testable android apps

DI != Dagger

Page 49: Testable android apps

The code that needs dependencies is not

responsible for getting them

Page 50: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives

you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 51: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives

you Object Seams, which is why MVP helps testability. Build Variants give you Link Seams, but don’t overuse

them.

Page 52: Testable android apps
Page 53: Testable android apps

private void setupCards(CollectionView.Inventory inventory) { if (SettingsUtils.isAttendeeAtVenue(getContext())) { if (!hasAnsweredConfMessageCardsPrompt(getContext())) { inventoryGroup = new InventoryGroup(GROUP_ID_MESSAGE_CARDS);

MessageData conferenceMessageOptIn = MessageCardHelper .getConferenceOptInMessageData(getContext()); inventoryGroup.addItemWithTag(conferenceMessageOptIn); inventoryGroup.setDisplayCols(1); inventory.addGroup(inventoryGroup); } // ... } }

Page 54: Testable android apps

private void setupCards(CollectionView.Inventory inventory) { if (SettingsUtils.isAttendeeAtVenue(getContext())) { if (!hasAnsweredConfMessageCardsPrompt(getContext())) { inventoryGroup = new InventoryGroup(GROUP_ID_MESSAGE_CARDS);

MessageData conferenceMessageOptIn = MessageCardHelper .getConferenceOptInMessageData(getContext()); inventoryGroup.addItemWithTag(conferenceMessageOptIn); inventoryGroup.setDisplayCols(1); inventory.addGroup(inventoryGroup); } // ... } }

Page 55: Testable android apps

private void setupCards(CollectionView.Inventory inventory) { if (SettingsUtils.isAttendeeAtVenue(getContext())) { if (!hasAnsweredConfMessageCardsPrompt(getContext())) { inventoryGroup = new InventoryGroup(GROUP_ID_MESSAGE_CARDS);

MessageData conferenceMessageOptIn = MessageCardHelper .getConferenceOptInMessageData(getContext()); inventoryGroup.addItemWithTag(conferenceMessageOptIn); inventoryGroup.setDisplayCols(1); inventory.addGroup(inventoryGroup); } // ... } }

Page 56: Testable android apps

class Presenter {

public void presentCards() {

if (mIsAttendeeAtVenue) {

if (!mMsgSettings.hasAnsweredMessagePrompt()) {

mExploreView.addMessageOptInCard();

} // Stuff } } }

Page 57: Testable android apps

class Presenter {

public void presentCards() {

if (mIsAttendeeAtVenue) {

if (!mMsgSettings.hasAnsweredMessagePrompt()) {

mExploreView.addMessageOptInCard();

} // Stuff } } }

Page 58: Testable android apps

class Presenter {

public void presentCards() {

if (mIsAttendeeAtVenue) {

if (!mMsgSettings.hasAnsweredMessagePrompt()) {

mExploreView.addMessageOptInCard();

} // Stuff } } }

Page 59: Testable android apps

class Presenter {

public void presentCards() {

if (mIsAttendeeAtVenue) {

if (!mMsgSettings.hasAnsweredMessagePrompt()) {

mExploreView.addMessageOptInCard();

} // Stuff } } }

Page 60: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives

you Object Seams, which is why MVP helps testability. Build Variants give you Link Seams, but don’t overuse

them.

Page 61: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 62: Testable android apps

–Michael Feathers

“[code] contains calls to code in other files. Linkers…resolve each of the calls so that you

can have a complete program at runtime…you can usually exploit [this] to substitute pieces of

your program”

Page 63: Testable android apps

Use Link Seams for Espresso Tests

Page 64: Testable android apps
Page 65: Testable android apps
Page 66: Testable android apps

public class PresenterFragmentImpl extends Fragment implements Presenter, UpdatableView.UserActionListener, LoaderManager.LoaderCallbacks<Cursor> {

@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Loader<Cursor> cursorLoader = createLoader(id, args); mLoaderIdlingResource.onLoaderStarted(cursorLoader); return cursorLoader; }

@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { processData(loader, data); mLoaderIdlingResource.onLoaderFinished(loader); } }

Page 67: Testable android apps

public class PresenterFragmentImpl extends Fragment implements Presenter, UpdatableView.UserActionListener, LoaderManager.LoaderCallbacks<Cursor> {

@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Loader<Cursor> cursorLoader = createLoader(id, args); mLoaderIdlingResource.onLoaderStarted(cursorLoader); return cursorLoader; }

@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { processData(loader, data); mLoaderIdlingResource.onLoaderFinished(loader); } }

Page 68: Testable android apps

public PresenterFragmentImpl addPresenterFragment(int uVResId, Model model, QueryEnum[] queries,

UserActionEnum[] actions){ //...

if (presenter == null) { //Create, set up and add the presenter. presenter = new PresenterFragmentImpl(); //... } else {

//... } return presenter; }

Page 69: Testable android apps

public PresenterFragmentImpl addPresenterFragment(int uVResId, Model model, QueryEnum[] queries,

UserActionEnum[] actions){ //...

if (presenter == null) { //Create, set up and add the presenter. presenter = new PresenterFragmentImpl(); //... } else {

//... } return presenter; }

Page 70: Testable android apps
Page 71: Testable android apps
Page 72: Testable android apps

flavorDimensions 'datasource', 'features' productFlavors { mock { dimension 'datasource' }

prod { dimension 'datasource' }

free { dimension 'features' } }

Page 73: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 74: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 75: Testable android apps

More complicated object graphs can lead to…

Page 76: Testable android apps
Page 77: Testable android apps

Use Link Seams to swap out factories so you can

use object seams

Page 78: Testable android apps

Use Link Seams to swap out factories so you can

use object seams

Page 79: Testable android apps

public class FragFactory { public PresenterFragmentImpl make() { return new PresenterFragmentImpl(); } }

public class FragFactory { public PresenterFragmentImpl make() { return new MockPresenterFragmentImpl(); } }

Page 80: Testable android apps

public class FragFactory { public PresenterFragmentImpl make() { return new PresenterFragmentImpl(); } }

public class FragFactory { public PresenterFragmentImpl make() { return new MockPresenterFragmentImpl(); } }

Page 81: Testable android apps

public class FragFactory { public PresenterFragmentImpl make() { return new PresenterFragmentImpl(); } }

public class FragFactory { public PresenterFragmentImpl make() { return new MockPresenterFragmentImpl(); } }

Page 82: Testable android apps

public PresenterFragmentImpl addPresenterFragment(int uVResId, Model model, QueryEnum[] queries,

UserActionEnum[] actions){ //...

if (presenter == null) { //Create, set up and add the presenter. presenter = new PresenterFragmentImpl(); // 1 seam //... } else {

//... } return presenter; }

Page 83: Testable android apps

public PresenterFragmentImpl addPresenterFragment(int uVResId, Model model, QueryEnum[] queries,

UserActionEnum[] actions){ //...

if (presenter == null) { //Create, set up and add the presenter. presenter = mFragFactory.make(); // 2 seams //... } else {

//... } return presenter; }

Page 84: Testable android apps

This second seam buys you “mock mode”

Page 85: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 86: Testable android apps

Tested apps are better apps, but building them is tough. They have seams. DI gives you Object Seams, which is why MVP helps testability.

Build Variants give you Link Seams, but don’t overuse

them.

Page 87: Testable android apps

Writing Testable Apps

Page 88: Testable android apps

• “Microservices: Software That Fits in Your Head”

• “Mark Zuckerberg: How to Build the Future”

• Growing Object Oriented Software Guided by Tests

• Working Effectively with Legacy Code

• “Dependency Injection” by Martin Fowler

• “Android Apps with Dagger” by Jake Wharton

Sources