SlideShare a Scribd company logo
1 of 88
Download to read offline
Writing Testable 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.
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.
Why tests?
Why tests?
Why are we here?
ā€œthe goal of
software delivery
is to sustainably
minimize the lead
time to business
impactā€
Yes, but why tests?
ā€“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.ā€
ā€œ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.ā€
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.
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.
ā€“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.ā€
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);
}
}
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);
}
}
What makes code
testable?
@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);
}
}
@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);
}
}
@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);
}
}
@Test
public void onSPChangedRemovesSessions() throws Exception {
// Arrange
//Act
mSettingsFragment.onSPChanged(mMockSharedPreferences,
PREF_SYNC_CALENDAR);
//Assert
}
@Test
public void onSPChangedRemovesSessions() throws Exception {
// Arrange
//Act
mSettingsFragment.onSPChanged(mMockSharedPreferences,
PREF_SYNC_CALENDAR);
//Assert
}
@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);
}
}
@Test
public void onSPChangedRemovesSessions() throws Exception {
// Arrange
//Act
mSettingsFragment.onSPChanged(mMockSharedPreferences,
PREF_SYNC_CALENDAR);
//Assert
}
@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);
}
}
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.
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.
ā€“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.ā€
Without seams, itā€™s often
difļ¬cult to arrange and/or
assert
class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
@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();
}
@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();
}
@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();
}
class CalendarUpdatingOnSharedPreferenceChangedListener {
void onPreferenceChanged(CalendarPreferences calendarPreferences,
String key) {
if (SettingsUtils.PREF_SYNC_CALENDAR.equals(key)) {
if (calendarPreferences.shouldSyncCalendar()) {
mSessUpdaterLauncher.launchAddAllSessionsUpdater();
} else {
mSessUpdaterLauncher.launchClearAllSessionsUpdate();
}
}
}
}
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.
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.
Object Seams
ā€“Michael Feathers
ā€œThe fundamental thing to recognize is that
when we look at a call in an object-oriented
program, it does not deļ¬ne which method will
actually be executed.ā€
DI != Dagger
The code that needs
dependencies is not
responsible for getting
them
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.
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.
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);
} // ...
}
}
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);
} // ...
}
}
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);
} // ...
}
}
class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
class Presenter {
public void presentCards() {
if (mIsAttendeeAtVenue) {
if (!mMsgSettings.hasAnsweredMessagePrompt()) {
mExploreView.addMessageOptInCard();
} // Stuff
}
}
}
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.
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.
ā€“Michael Feathers
ā€œ[code] contains calls to code in other ļ¬les.
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ā€
Use Link Seams for
Espresso Tests
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);
}
}
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);
}
}
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;
}
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;
}
flavorDimensions 'datasource', 'features'
productFlavors {
mock {
dimension 'datasource'
}
prod {
dimension 'datasource'
}
free {
dimension 'features'
}
}
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.
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.
More complicated object
graphs can lead toā€¦
Use Link Seams to swap
out factories so you can
use object seams
Use Link Seams to swap
out factories so you can
use object seams
public class FragFactory {
public PresenterFragmentImpl make() {
return new PresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new MockPresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new PresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new MockPresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new PresenterFragmentImpl();
}
}
public class FragFactory {
public PresenterFragmentImpl make() {
return new MockPresenterFragmentImpl();
}
}
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;
}
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;
}
This second seam buys
you ā€œmock modeā€
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.
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.
Writing Testable 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

More Related Content

What's hot

Tempoā€™s Journey Into the Cloud
Tempoā€™s Journey Into the CloudTempoā€™s Journey Into the Cloud
Tempoā€™s Journey Into the CloudAtlassian
Ā 
Atlassian Connect on Serverless Platforms: Low Cost Add-Ons
Atlassian Connect on Serverless Platforms: Low Cost Add-OnsAtlassian Connect on Serverless Platforms: Low Cost Add-Ons
Atlassian Connect on Serverless Platforms: Low Cost Add-OnsAtlassian
Ā 
You've Made Kubernetes Available to Your Developers, Now What?
You've Made Kubernetes Available to Your Developers, Now What?You've Made Kubernetes Available to Your Developers, Now What?
You've Made Kubernetes Available to Your Developers, Now What?cornelia davis
Ā 
Integrating Jira Software Cloud With the AWS Code Suite
Integrating Jira Software Cloud With the AWS Code SuiteIntegrating Jira Software Cloud With the AWS Code Suite
Integrating Jira Software Cloud With the AWS Code SuiteAtlassian
Ā 
Shipping to Server and Cloud with Docker
Shipping to Server and Cloud with DockerShipping to Server and Cloud with Docker
Shipping to Server and Cloud with DockerAtlassian
Ā 
Leaning into Server to Cloud App Migration
Leaning into Server to Cloud App MigrationLeaning into Server to Cloud App Migration
Leaning into Server to Cloud App MigrationAtlassian
Ā 

What's hot (9)

Tempoā€™s Journey Into the Cloud
Tempoā€™s Journey Into the CloudTempoā€™s Journey Into the Cloud
Tempoā€™s Journey Into the Cloud
Ā 
Atlassian Connect on Serverless Platforms: Low Cost Add-Ons
Atlassian Connect on Serverless Platforms: Low Cost Add-OnsAtlassian Connect on Serverless Platforms: Low Cost Add-Ons
Atlassian Connect on Serverless Platforms: Low Cost Add-Ons
Ā 
You've Made Kubernetes Available to Your Developers, Now What?
You've Made Kubernetes Available to Your Developers, Now What?You've Made Kubernetes Available to Your Developers, Now What?
You've Made Kubernetes Available to Your Developers, Now What?
Ā 
Vue.js 101
Vue.js 101Vue.js 101
Vue.js 101
Ā 
AWS CodeDeploy
AWS CodeDeployAWS CodeDeploy
AWS CodeDeploy
Ā 
Grails with swagger
Grails with swaggerGrails with swagger
Grails with swagger
Ā 
Integrating Jira Software Cloud With the AWS Code Suite
Integrating Jira Software Cloud With the AWS Code SuiteIntegrating Jira Software Cloud With the AWS Code Suite
Integrating Jira Software Cloud With the AWS Code Suite
Ā 
Shipping to Server and Cloud with Docker
Shipping to Server and Cloud with DockerShipping to Server and Cloud with Docker
Shipping to Server and Cloud with Docker
Ā 
Leaning into Server to Cloud App Migration
Leaning into Server to Cloud App MigrationLeaning into Server to Cloud App Migration
Leaning into Server to Cloud App Migration
Ā 

Similar to Writing testable android apps

The Quest for Continuous Delivery at Pluralsight
The Quest for Continuous Delivery at PluralsightThe Quest for Continuous Delivery at Pluralsight
The Quest for Continuous Delivery at PluralsightMike Clement
Ā 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto UniversitySC5.io
Ā 
Reactive programming with RxJS - Taiwan
Reactive programming with RxJS - TaiwanReactive programming with RxJS - Taiwan
Reactive programming with RxJS - Taiwanmodernweb
Ā 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy CodeNaresh Jain
Ā 
ML-Ops how to bring your data science to production
ML-Ops  how to bring your data science to productionML-Ops  how to bring your data science to production
ML-Ops how to bring your data science to productionHerman Wu
Ā 
Testing your application on Google App Engine
Testing your application on Google App EngineTesting your application on Google App Engine
Testing your application on Google App EngineInphina Technologies
Ā 
Testing Your Application On Google App Engine
Testing Your Application On Google App EngineTesting Your Application On Google App Engine
Testing Your Application On Google App EngineIndicThreads
Ā 
Pragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScriptPragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScriptdavejohnson
Ā 
Refactoring Wunderlist. UA Mobile 2016.
Refactoring Wunderlist. UA Mobile 2016.Refactoring Wunderlist. UA Mobile 2016.
Refactoring Wunderlist. UA Mobile 2016.UA Mobile
Ā 
#DOAW16 - DevOps@work Roma 2016 - Testing your databases
#DOAW16 - DevOps@work Roma 2016 - Testing your databases#DOAW16 - DevOps@work Roma 2016 - Testing your databases
#DOAW16 - DevOps@work Roma 2016 - Testing your databasesAlessandro Alpi
Ā 
The Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs PublicThe Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs PublicDavid Solivan
Ā 
Modular programming Using Object in Scala
Modular programming Using Object in ScalaModular programming Using Object in Scala
Modular programming Using Object in ScalaKnoldus Inc.
Ā 
Javascript-heavy Salesforce Applications
Javascript-heavy Salesforce ApplicationsJavascript-heavy Salesforce Applications
Javascript-heavy Salesforce ApplicationsSalesforce Developers
Ā 
Arquillian & Citrus
Arquillian & CitrusArquillian & Citrus
Arquillian & Citruschristophd
Ā 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsJeff Durta
Ā 
Clean Architecture @ Taxibeat
Clean Architecture @ TaxibeatClean Architecture @ Taxibeat
Clean Architecture @ TaxibeatMichael Bakogiannis
Ā 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...Fabio Franzini
Ā 
Microservices with .Net - NDC Sydney, 2016
Microservices with .Net - NDC Sydney, 2016Microservices with .Net - NDC Sydney, 2016
Microservices with .Net - NDC Sydney, 2016Richard Banks
Ā 

Similar to Writing testable android apps (20)

The Quest for Continuous Delivery at Pluralsight
The Quest for Continuous Delivery at PluralsightThe Quest for Continuous Delivery at Pluralsight
The Quest for Continuous Delivery at Pluralsight
Ā 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto University
Ā 
Reactive programming with RxJS - Taiwan
Reactive programming with RxJS - TaiwanReactive programming with RxJS - Taiwan
Reactive programming with RxJS - Taiwan
Ā 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Code
Ā 
ML-Ops how to bring your data science to production
ML-Ops  how to bring your data science to productionML-Ops  how to bring your data science to production
ML-Ops how to bring your data science to production
Ā 
Testing your application on Google App Engine
Testing your application on Google App EngineTesting your application on Google App Engine
Testing your application on Google App Engine
Ā 
Testing Your Application On Google App Engine
Testing Your Application On Google App EngineTesting Your Application On Google App Engine
Testing Your Application On Google App Engine
Ā 
Pragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScriptPragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScript
Ā 
Pyramid patterns
Pyramid patternsPyramid patterns
Pyramid patterns
Ā 
Refactoring Wunderlist. UA Mobile 2016.
Refactoring Wunderlist. UA Mobile 2016.Refactoring Wunderlist. UA Mobile 2016.
Refactoring Wunderlist. UA Mobile 2016.
Ā 
#DOAW16 - DevOps@work Roma 2016 - Testing your databases
#DOAW16 - DevOps@work Roma 2016 - Testing your databases#DOAW16 - DevOps@work Roma 2016 - Testing your databases
#DOAW16 - DevOps@work Roma 2016 - Testing your databases
Ā 
The Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs PublicThe Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs Public
Ā 
Modular programming Using Object in Scala
Modular programming Using Object in ScalaModular programming Using Object in Scala
Modular programming Using Object in Scala
Ā 
Design for Testability
Design for TestabilityDesign for Testability
Design for Testability
Ā 
Javascript-heavy Salesforce Applications
Javascript-heavy Salesforce ApplicationsJavascript-heavy Salesforce Applications
Javascript-heavy Salesforce Applications
Ā 
Arquillian & Citrus
Arquillian & CitrusArquillian & Citrus
Arquillian & Citrus
Ā 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applications
Ā 
Clean Architecture @ Taxibeat
Clean Architecture @ TaxibeatClean Architecture @ Taxibeat
Clean Architecture @ Taxibeat
Ā 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
Ā 
Microservices with .Net - NDC Sydney, 2016
Microservices with .Net - NDC Sydney, 2016Microservices with .Net - NDC Sydney, 2016
Microservices with .Net - NDC Sydney, 2016
Ā 

More from K. Matthew Dupree

intro-to-metaprogramming-in-r.pdf
intro-to-metaprogramming-in-r.pdfintro-to-metaprogramming-in-r.pdf
intro-to-metaprogramming-in-r.pdfK. Matthew Dupree
Ā 
Intro To Gradient Descent in Javascript
Intro To Gradient Descent in JavascriptIntro To Gradient Descent in Javascript
Intro To Gradient Descent in JavascriptK. Matthew Dupree
Ā 
Dagger 2, 2 years later
Dagger 2, 2 years laterDagger 2, 2 years later
Dagger 2, 2 years laterK. Matthew Dupree
Ā 
An Introduction to RxJava
An Introduction to RxJavaAn Introduction to RxJava
An Introduction to RxJavaK. Matthew Dupree
Ā 
If Android Tests Could Talk
If Android Tests Could TalkIf Android Tests Could Talk
If Android Tests Could TalkK. Matthew Dupree
Ā 
Functional Testing for React Native Apps
Functional Testing for React Native AppsFunctional Testing for React Native Apps
Functional Testing for React Native AppsK. Matthew Dupree
Ā 

More from K. Matthew Dupree (8)

intro-to-metaprogramming-in-r.pdf
intro-to-metaprogramming-in-r.pdfintro-to-metaprogramming-in-r.pdf
intro-to-metaprogramming-in-r.pdf
Ā 
Intro To Gradient Descent in Javascript
Intro To Gradient Descent in JavascriptIntro To Gradient Descent in Javascript
Intro To Gradient Descent in Javascript
Ā 
Dagger 2, 2 years later
Dagger 2, 2 years laterDagger 2, 2 years later
Dagger 2, 2 years later
Ā 
An Introduction to RxJava
An Introduction to RxJavaAn Introduction to RxJava
An Introduction to RxJava
Ā 
If Android Tests Could Talk
If Android Tests Could TalkIf Android Tests Could Talk
If Android Tests Could Talk
Ā 
Di and Dagger
Di and DaggerDi and Dagger
Di and Dagger
Ā 
Functional Testing for React Native Apps
Functional Testing for React Native AppsFunctional Testing for React Native Apps
Functional Testing for React Native Apps
Ā 
Testable android apps
Testable android appsTestable android apps
Testable android apps
Ā 

Recently uploaded

FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | DelhiFULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhisoniya singh
Ā 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
Ā 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
Ā 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
Ā 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
Ā 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
Ā 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
Ā 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?XfilesPro
Ā 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
Ā 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
Ā 
Hyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your BudgetHyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your BudgetEnjoy Anytime
Ā 
Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Hyundai Motor Group
Ā 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraDeakin University
Ā 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
Ā 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
Ā 
Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...
Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...
Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...Patryk Bandurski
Ā 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
Ā 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
Ā 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
Ā 

Recently uploaded (20)

Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Ā 
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | DelhiFULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY šŸ” 8264348440 šŸ” Call Girls in Diplomatic Enclave | Delhi
Ā 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
Ā 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Ā 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
Ā 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Ā 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
Ā 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
Ā 
How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?How to Remove Document Management Hurdles with X-Docs?
How to Remove Document Management Hurdles with X-Docs?
Ā 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
Ā 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
Ā 
Hyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your BudgetHyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your Budget
Hyderabad Call Girls Khairatabad āœØ 7001305949 āœØ Cheap Price Your Budget
Ā 
Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2
Ā 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning era
Ā 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
Ā 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
Ā 
Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...
Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...
Integration and Automation in Practice: CI/CD in MuleĀ Integration and Automat...
Ā 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
Ā 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
Ā 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Ā 

Writing testable android apps

  • 1.
  • 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.
  • 7. Why are we here?
  • 8. ā€œthe goal of software delivery is to sustainably minimize the lead time to business impactā€
  • 9. Yes, but why tests?
  • 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); } }
  • 21.
  • 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. @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); } }
  • 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. @Test public void onSPChangedRemovesSessions() throws Exception { // Arrange //Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR); //Assert }
  • 26. @Test public void onSPChangedRemovesSessions() throws Exception { // Arrange //Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR); //Assert }
  • 27. @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); } }
  • 28. @Test public void onSPChangedRemovesSessions() throws Exception { // Arrange //Act mSettingsFragment.onSPChanged(mMockSharedPreferences, PREF_SYNC_CALENDAR); //Assert }
  • 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.ā€
  • 34. Without seams, itā€™s often difļ¬cult to arrange and/or assert
  • 35.
  • 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 deļ¬ne 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 ļ¬les. 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ā€
  • 63. Use Link Seams for Espresso Tests
  • 64.
  • 65.
  • 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; }
  • 70.
  • 71.
  • 72. flavorDimensions 'datasource', 'features' productFlavors { mock { dimension 'datasource' } prod { dimension 'datasource' } free { dimension 'features' } }
  • 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.
  • 75. More complicated object graphs can lead toā€¦
  • 76.
  • 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; }
  • 84. This second seam buys you ā€œmock modeā€
  • 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