Tested android apps are better apps, but building them is tough. This talk is about how to write testable Android applications. Testable apps have seams, which you can get using DI and Build Variants.
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. 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.
10. –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.”
11. “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.”
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. 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.
14. –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.”
15.
16.
17.
18. 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);
}
}
19. 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);
}
}
29. @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);
}
}
30. 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.
31. 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.
32.
33. –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.”
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. class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
38. class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
39. class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
40. @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();
}
41. @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();
}
42. @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();
}
43. class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
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. 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.
47. –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.”
49. The code that needs
dependencies is not
responsible for getting
them
50. 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.
51. 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.
52.
53. 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);
} // ...
}
}
54. 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);
} // ...
}
}
55. 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);
} // ...
}
}
56. class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
57. class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
58. class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
59. class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
60. 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.
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. –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”
66. 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);
}
}
67. 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);
}
}
68. 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;
}
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();
//...
} else {
//...
}
return presenter;
}
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. 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.
77. Use Link Seams to swap
out factories so you can
use object seams
78. Use Link Seams to swap
out factories so you can
use object seams
79. public class FragFactory {
public PresenterFragmentImpl make() {
return new PresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new MockPresenterFragmentImpl();
}
}
80. public class FragFactory {
public PresenterFragmentImpl make() {
return new PresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new MockPresenterFragmentImpl();
}
}
81. public class FragFactory {
public PresenterFragmentImpl make() {
return new PresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new MockPresenterFragmentImpl();
}
}
82. 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;
}
83. 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;
}
85. 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.
86. 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.
88. • “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