SlideShare a Scribd company logo
Writing testable android apps
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.”
Writing testable android apps
Writing testable android apps
Writing testable android apps
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?
Writing testable android apps
@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.
Writing testable android apps
–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
Writing testable android apps
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.
Writing testable android apps
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
Writing testable android apps
Writing testable android apps
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;
}
Writing testable android apps
Writing testable android apps
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…
Writing testable android apps
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

PDF
Integration Testing on Steroids: Run Your Tests on the Real Things
PPT
Mobicents JSLEE progress and roadmap - Mobicents Summit 2011
PDF
Resilience Engineering: A field of study, a community, and some perspective s...
PDF
Using Task Queues and D3.js to build an analytics product on App Engine
PDF
Introduction to Struts
PDF
Spec-first API Design for Speed and Safety
PDF
Building a Cerberus App Without Losing Our Heads: The Passage to a Cross-Plat...
PDF
From AUI to Atlaskit - Streamlining Development for Server & Cloud Apps
Integration Testing on Steroids: Run Your Tests on the Real Things
Mobicents JSLEE progress and roadmap - Mobicents Summit 2011
Resilience Engineering: A field of study, a community, and some perspective s...
Using Task Queues and D3.js to build an analytics product on App Engine
Introduction to Struts
Spec-first API Design for Speed and Safety
Building a Cerberus App Without Losing Our Heads: The Passage to a Cross-Plat...
From AUI to Atlaskit - Streamlining Development for Server & Cloud Apps

What's hot (9)

PDF
Tempo’s Journey Into the Cloud
PDF
Atlassian Connect on Serverless Platforms: Low Cost Add-Ons
PDF
You've Made Kubernetes Available to Your Developers, Now What?
PDF
Vue.js 101
PPTX
AWS CodeDeploy
PPTX
Grails with swagger
PDF
Integrating Jira Software Cloud With the AWS Code Suite
PDF
Shipping to Server and Cloud with Docker
PDF
Leaning into Server to Cloud App Migration
Tempo’s Journey Into the Cloud
Atlassian Connect on Serverless Platforms: Low Cost Add-Ons
You've Made Kubernetes Available to Your Developers, Now What?
Vue.js 101
AWS CodeDeploy
Grails with swagger
Integrating Jira Software Cloud With the AWS Code Suite
Shipping to Server and Cloud with Docker
Leaning into Server to Cloud App Migration
Ad

Similar to Writing testable android apps (20)

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

More from K. Matthew Dupree (9)

PDF
Generating Union types w/ Static Analysis
PDF
intro-to-metaprogramming-in-r.pdf
PDF
Intro To Gradient Descent in Javascript
PDF
Dagger 2, 2 years later
PDF
An Introduction to RxJava
PDF
If Android Tests Could Talk
PDF
Di and Dagger
PDF
Functional Testing for React Native Apps
PDF
Testable android apps
Generating Union types w/ Static Analysis
intro-to-metaprogramming-in-r.pdf
Intro To Gradient Descent in Javascript
Dagger 2, 2 years later
An Introduction to RxJava
If Android Tests Could Talk
Di and Dagger
Functional Testing for React Native Apps
Testable android apps

Recently uploaded (20)

PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Network Security Unit 5.pdf for BCA BBA.
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Electronic commerce courselecture one. Pdf
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Dropbox Q2 2025 Financial Results & Investor Presentation
Network Security Unit 5.pdf for BCA BBA.
Programs and apps: productivity, graphics, security and other tools
Review of recent advances in non-invasive hemoglobin estimation
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
“AI and Expert System Decision Support & Business Intelligence Systems”
Mobile App Security Testing_ A Comprehensive Guide.pdf
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
The Rise and Fall of 3GPP – Time for a Sabbatical?
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Diabetes mellitus diagnosis method based random forest with bat algorithm
Electronic commerce courselecture one. Pdf
Per capita expenditure prediction using model stacking based on satellite ima...

Writing testable android apps

  • 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.”
  • 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); } }
  • 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.
  • 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
  • 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.
  • 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
  • 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; }
  • 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.
  • 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