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
difficult 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 define 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 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”
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
 
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

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

Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...apidays
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxRustici Software
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Zilliz
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWERMadyBayot
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfOverkill Security
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherRemote DBA Services
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbuapidays
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobeapidays
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businesspanagenda
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
A Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source MilvusA Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source MilvusZilliz
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024The Digital Insurer
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 

Recently uploaded (20)

Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
A Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source MilvusA Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source Milvus
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 

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 difficult 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 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”
  • 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.
  • 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