Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Writing testable android apps

18 views

Published on

How to write testable android apps

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Writing testable android apps

  1. 1. Writing Testable Apps
  2. 2. 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.
  3. 3. 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.
  4. 4. Why tests?
  5. 5. Why tests?
  6. 6. Why are we here?
  7. 7. “the goal of software delivery is to sustainably minimize the lead time to business impact”
  8. 8. Yes, but why tests?
  9. 9. –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.”
  10. 10. “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.”
  11. 11. 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.
  12. 12. 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.
  13. 13. –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.”
  14. 14. 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); } }
  15. 15. 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); } }
  16. 16. What makes code testable?
  17. 17. @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); } }
  18. 18. @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); } }
  19. 19. @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); } }
  20. 20. @Test public void onSPChangedRemovesSessions() throws Exception { // Arrange //Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR); //Assert }
  21. 21. @Test public void onSPChangedRemovesSessions() throws Exception { // Arrange //Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR); //Assert }
  22. 22. @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); } }
  23. 23. @Test public void onSPChangedRemovesSessions() throws Exception { // Arrange //Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR); //Assert }
  24. 24. @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); } }
  25. 25. 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.
  26. 26. 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.
  27. 27. –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.”
  28. 28. Without seams, it’s often difficult to arrange and/or assert
  29. 29. class CalendarUpdatingOnSharedPreferenceChangedListener { void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) { if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }
  30. 30. class CalendarUpdatingOnSharedPreferenceChangedListener { void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) { if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }
  31. 31. class CalendarUpdatingOnSharedPreferenceChangedListener { void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) { if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }
  32. 32. class CalendarUpdatingOnSharedPreferenceChangedListener { void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) { if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }
  33. 33. @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(); }
  34. 34. @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(); }
  35. 35. @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(); }
  36. 36. class CalendarUpdatingOnSharedPreferenceChangedListener { void onPreferenceChanged(CalendarPreferences calendarPreferences, String key) { if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) { if (calendarPreferences.shouldSyncCalendar()) { mSessUpdaterLauncher.launchAddAllSessionsUpdater(); } else { mSessUpdaterLauncher.launchClearAllSessionsUpdate(); } } } }
  37. 37. 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.
  38. 38. 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.
  39. 39. Object Seams
  40. 40. –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.”
  41. 41. DI != Dagger
  42. 42. The code that needs dependencies is not responsible for getting them
  43. 43. 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.
  44. 44. 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.
  45. 45. 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); } // ... } }
  46. 46. 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); } // ... } }
  47. 47. 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); } // ... } }
  48. 48. class Presenter { public void presentCards() { if (mIsAttendeeAtVenue) { if (!mMsgSettings.hasAnsweredMessagePrompt()) { mExploreView.addMessageOptInCard(); } // Stuff } } }
  49. 49. class Presenter { public void presentCards() { if (mIsAttendeeAtVenue) { if (!mMsgSettings.hasAnsweredMessagePrompt()) { mExploreView.addMessageOptInCard(); } // Stuff } } }
  50. 50. class Presenter { public void presentCards() { if (mIsAttendeeAtVenue) { if (!mMsgSettings.hasAnsweredMessagePrompt()) { mExploreView.addMessageOptInCard(); } // Stuff } } }
  51. 51. class Presenter { public void presentCards() { if (mIsAttendeeAtVenue) { if (!mMsgSettings.hasAnsweredMessagePrompt()) { mExploreView.addMessageOptInCard(); } // Stuff } } }
  52. 52. 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.
  53. 53. 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.
  54. 54. –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”
  55. 55. Use Link Seams for Espresso Tests
  56. 56. 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); } }
  57. 57. 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); } }
  58. 58. 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; }
  59. 59. 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; }
  60. 60. flavorDimensions 'datasource', 'features' productFlavors { mock { dimension 'datasource' } prod { dimension 'datasource' } free { dimension 'features' } }
  61. 61. 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.
  62. 62. 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.
  63. 63. More complicated object graphs can lead to…
  64. 64. Use Link Seams to swap out factories so you can use object seams
  65. 65. Use Link Seams to swap out factories so you can use object seams
  66. 66. public class FragFactory { public PresenterFragmentImpl make() { return new PresenterFragmentImpl(); } } public class FragFactory { public PresenterFragmentImpl make() { return new MockPresenterFragmentImpl(); } }
  67. 67. public class FragFactory { public PresenterFragmentImpl make() { return new PresenterFragmentImpl(); } } public class FragFactory { public PresenterFragmentImpl make() { return new MockPresenterFragmentImpl(); } }
  68. 68. public class FragFactory { public PresenterFragmentImpl make() { return new PresenterFragmentImpl(); } } public class FragFactory { public PresenterFragmentImpl make() { return new MockPresenterFragmentImpl(); } }
  69. 69. 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; }
  70. 70. 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; }
  71. 71. This second seam buys you “mock mode”
  72. 72. 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.
  73. 73. 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.
  74. 74. Writing Testable Apps
  75. 75. • “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

×