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

AWS CodeDeploy
AWS CodeDeployAWS CodeDeploy
AWS CodeDeploy
TO THE NEW | Technology
 

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

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
 

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

Recently uploaded (20)

Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot ModelNavi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Navi Mumbai Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
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...
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
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
 
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
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...
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 

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