If Android Tests
Could Talk
Matt Dupree
Their pace has quickened
This code is garbage
–Steve Freeman and Nat Pryce, 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.”
–Steve Freeman and Nat Pryce, 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.”
–Steve Freeman and Nat Pryce, 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.”
Activities tend to have tight
coupling and poor cohesion
Application components (activities,
services, providers, receivers) are interfaces
for your application to interact with the
operating system; don’t take them as a
recommendation of the facilities you
should architect your entire application
around.
—Chet Haase, Developing for Android VII
Activities tend to have tight
coupling and poor cohesion
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (requestCode != REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

if (mMapFragment != null) {

mMapFragment.setMyLocationEnabled(true);

}

} else {

// Permission was denied. Display error message.

Toast.makeText(this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}

super.onRequestPermissionsResult(requestCode, permissions,

grantResults);

}
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (requestCode != REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

if (mMapFragment != null) {

mMapFragment.setMyLocationEnabled(true);

}

} else {

// Permission was denied. Display error message.

Toast.makeText(this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}

super.onRequestPermissionsResult(requestCode, permissions,

grantResults);

}
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (requestCode != REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

if (mMapFragment != null) {

mMapFragment.setMyLocationEnabled(true);

}

} else {

// Permission was denied. Display error message.

Toast.makeText(this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}

super.onRequestPermissionsResult(requestCode, permissions,

grantResults);

}
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (requestCode != REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

if (mMapFragment != null) {

mMapFragment.setMyLocationEnabled(true);

}

} else {

// Permission was denied. Display error message.

Toast.makeText(this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}

super.onRequestPermissionsResult(requestCode, permissions,

grantResults);

}
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (requestCode != REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

if (mMapFragment != null) {

mMapFragment.setMyLocationEnabled(true);

}

} else {

// Permission was denied. Display error message.

Toast.makeText(this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}

super.onRequestPermissionsResult(requestCode, permissions,

grantResults);

}
@Test

public void showsToastIfPermissionIsRejected()

throws Exception {

MapActivity mapActivity = new MapActivity();



mapActivity.onRequestPermissionsResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{

PackageManager.PERMISSION_DENIED});



assertToastDisplayed();

}
@Test

public void showsToastIfPermissionIsRejected()

throws Exception {

MapActivity mapActivity = new MapActivity();



mapActivity.onRequestPermissionsResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{

PackageManager.PERMISSION_DENIED});



assertToastDisplayed();

}
@Test

public void showsToastIfPermissionIsRejected()

throws Exception {

MapActivity mapActivity = new MapActivity();



mapActivity.onRequestPermissionsResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{

PackageManager.PERMISSION_DENIED});



assertToastDisplayed();

}
@Test

public void showsToastIfPermissionIsRejected()

throws Exception {

MapActivity mapActivity = new MapActivity();



mapActivity.onRequestPermissionsResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{

PackageManager.PERMISSION_DENIED});



assertToastDisplayed();

}
private void assertToastDisplayed() {

// It can't be done, sir 

}
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (requestCode != REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

if (mMapFragment != null) {

mMapFragment.setMyLocationEnabled(true);

}

} else {

// Permission was denied. Display error message.

Toast.makeText(this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}

super.onRequestPermissionsResult(requestCode, permissions,

grantResults);

}
MapActivity is tightly coupled
with MapFragment and Toast
static class OnPermissionResultListener {


private final PermittedView mPermittedView;



void onPermissionResult(final int requestCode,

final String[] permissions, final int[] grantResults) {}



interface PermittedView {


void displayPermissionDenied();
void displayPermittedView();

}

}
static class OnPermissionResultListener {


private final PermittedView mPermittedView;



void onPermissionResult(final int requestCode,

final String[] permissions, final int[] grantResults) {}



interface PermittedView {


void displayPermissionDenied();
void displayPermittedView();

}

}
static class OnPermissionResultListener {


private final PermittedView mPermittedView;



void onPermissionResult(final int requestCode,

final String[] permissions, final int[] grantResults) {}



interface PermittedView {


void displayPermissionDenied();
void displayPermittedView();

}

}
void onPermissionResult(final int requestCode,

final String[] permissions, final int[] grantResults) {

if (requestCode != MapActivity.REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

MapActivity.LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

mPermittedView.displayPermittedView();



} else {

// Permission was denied. Display error message.

mPermittedView.displayPermissionDenied();

}

}
void onPermissionResult(final int requestCode,

final String[] permissions, final int[] grantResults) {

if (requestCode != MapActivity.REQUEST_LOCATION_PERMISSION) {

return;

}



if (permissions.length == 1 &&

MapActivity.LOCATION_PERMISSION.equals(permissions[0]) &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// Permission has been granted.

mPermittedView.displayPermittedView();



} else {

// Permission was denied. Display error message.

mPermittedView.displayPermissionDenied();

}

}
@Test

public void displaysErrorWhenPermissionRejected() throws Exception {



OnPermissionResultListener onPermissionResultListener =

new OnPermissionResultListener(mPermittedView);



onPermissionResultListener.onPermissionResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION},

new int[]{PackageManager.PERMISSION_DENIED});



verify(mPermittedView).displayPermissionDenied();

}
@Test

public void displaysErrorWhenPermissionRejected() throws Exception {



OnPermissionResultListener onPermissionResultListener =

new OnPermissionResultListener(mPermittedView);



onPermissionResultListener.onPermissionResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION},

new int[]{PackageManager.PERMISSION_DENIED});



verify(mPermittedView).displayPermissionDenied();

}
@Test

public void displaysErrorWhenPermissionRejected() throws Exception {



OnPermissionResultListener onPermissionResultListener =

new OnPermissionResultListener(mPermittedView);



onPermissionResultListener.onPermissionResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION},

new int[]{PackageManager.PERMISSION_DENIED});



verify(mPermittedView).displayPermissionDenied();

}
@Test

public void displaysErrorWhenPermissionRejected() throws Exception {



OnPermissionResultListener onPermissionResultListener =

new OnPermissionResultListener(mPermittedView);



onPermissionResultListener.onPermissionResult(

MapActivity.REQUEST_LOCATION_PERMISSION,

new String[]{MapActivity.LOCATION_PERMISSION},

new int[]{PackageManager.PERMISSION_DENIED});



verify(mPermittedView).displayPermissionDenied();

}
Who cares?
–Edward Yourdon and Larry Constantine
“what we are striving for is loosely coupled
systems…in which one can study (or debug, or
maintain) any one module without having to
know very much about any other modules in the
system”
@Override

public void displayPermissionDenied() {

Toast.makeText(MapActivity.this, R.string.map_permission_denied,

Toast.LENGTH_SHORT).show();

}
@Override

public void displayPermissionDenied() {

PermissionsUtils.displayConditionalPermissionDenialSnackbar(this,

R.string.map_permission_denied, PERMISSIONS,

REQUEST_LOCATION_PERMISSION, false);

}
–Kent Beck, TDD By Example
“Dependency is the key problem in software
development at all scales…if dependency is the
problem, duplication is the symptom.”
@Override

public void onRequestPermissionsResult(final int requestCode,

@NonNull final String[] permissions,

@NonNull final int[] grantResults) {



if (grantResults.length == APP_REQUIRED_PERMISSIONS.length &&

grantResults[0] == PackageManager.PERMISSION_GRANTED &&

grantResults[1] == PackageManager.PERMISSION_GRANTED) {

// If permission granted then refresh account list so user can

// select an account.

clearSnackbar();

reloadAccounts();

refreshAccountListUI();

} else {

LOGI(TAG, "onRequestPermissionResult with permissions
denied");

mSnackbar = new WeakReference<>(PermissionsUtils

.displayConditionalPermissionDenialSnackbar(

getActivity(),

R.string.welcome_permissions_rationale,

APP_REQUIRED_PERMISSIONS,

REQUEST_PERMISSION_REQUEST_CODE));

}

}
Activities tend to have tight
coupling and poor cohesion
–Steve Freeman and Nat Pryce, 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.”
Activities tend to have tight
coupling and poor cohesion
–Steve Freeman and Nat Pryce, 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.”
private void displaySessionData(final SessionDetailModel data) {
//46 statement method inside a 900 line class




// Handle Keynote as a special case, where the user cannot remove it

// from the schedule (it is auto added to schedule on sync)

mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&

!data.isKeynote());

mAddScheduleFab

.setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);



if (!data.isKeynote()) {

showInScheduleDeferred(data.isInSchedule());

}

//...


updateTimeBasedUi(data);





}
private void displaySessionData(final SessionDetailModel data) {
//46 statement method inside a 900 line class!




// Handle Keynote as a special case, where the user cannot remove it

// from the schedule (it is auto added to schedule on sync)

mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&

!data.isKeynote());

mAddScheduleFab

.setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);



if (!data.isKeynote()) {

showInScheduleDeferred(data.isInSchedule());

}

//...


updateTimeBasedUi(data);





}
private void displaySessionData(final SessionDetailModel data) {
//46 statement method inside a 900 line class




// Handle Keynote as a special case, where the user cannot remove it

// from the schedule (it is auto added to schedule on sync)

mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&

!data.isKeynote());

mAddScheduleFab

.setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);



if (!data.isKeynote()) {

showInScheduleDeferred(data.isInSchedule());

}

//...


updateTimeBasedUi(data);





}
private void displaySessionData(final SessionDetailModel data) {
//46 statement method inside a 900 line class




// Handle Keynote as a special case, where the user cannot remove it

// from the schedule (it is auto added to schedule on sync)

mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&

!data.isKeynote());

mAddScheduleFab

.setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);



if (!data.isKeynote()) {

showInScheduleDeferred(data.isInSchedule());

}

//...


updateTimeBasedUi(data);





}
private void displaySessionData(final SessionDetailModel data) {
//46 statement method inside a 900 line class




// Handle Keynote as a special case, where the user cannot remove it

// from the schedule (it is auto added to schedule on sync)

mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&

!data.isKeynote());

mAddScheduleFab

.setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);



if (!data.isKeynote()) {

showInScheduleDeferred(data.isInSchedule());

}

//...


updateTimeBasedUi(data);





}
@Test

public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()

throws Exception {

SessionDetailFragment sessionDetailFragment =

new SessionDetailFragment();

final SessionDetailModel sessionDetailModel =

mock(SessionDetailModel.class);

when(sessionDetailModel.isKeynote()).thenReturn(false);

sessionDetailFragment.displayData(sessionDetailModel,

SessionDetailModel.SessionDetailQueryEnum.SESSIONS);

final View addScheduleButton =

sessionDetailFragment.getView()

.findViewById(R.id.add_schedule_button);
assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);

}
@Test

public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()

throws Exception {

SessionDetailFragment sessionDetailFragment =

new SessionDetailFragment();

final SessionDetailModel sessionDetailModel =

mock(SessionDetailModel.class);

when(sessionDetailModel.isKeynote()).thenReturn(false);

sessionDetailFragment.displayData(sessionDetailModel,

SessionDetailModel.SessionDetailQueryEnum.SESSIONS);

final View addScheduleButton =

sessionDetailFragment.getView()

.findViewById(R.id.add_schedule_button);
assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);

}
@Test

public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()

throws Exception {

SessionDetailFragment sessionDetailFragment =

new SessionDetailFragment();

final SessionDetailModel sessionDetailModel =

mock(SessionDetailModel.class);

when(sessionDetailModel.isKeynote()).thenReturn(false);

sessionDetailFragment.displayData(sessionDetailModel,

SessionDetailModel.SessionDetailQueryEnum.SESSIONS);

final View addScheduleButton =

sessionDetailFragment.getView()

.findViewById(R.id.add_schedule_button);
assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);

}
@Test

public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()

throws Exception {

SessionDetailFragment sessionDetailFragment =

new SessionDetailFragment();

final SessionDetailModel sessionDetailModel =

mock(SessionDetailModel.class);

when(sessionDetailModel.isKeynote()).thenReturn(false);

sessionDetailFragment.displayData(sessionDetailModel,

SessionDetailModel.SessionDetailQueryEnum.SESSIONS);

final View addScheduleButton =

sessionDetailFragment.getView()

.findViewById(R.id.add_schedule_button);
assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);

}
@Test

public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()

throws Exception {

SessionDetailFragment sessionDetailFragment =

new SessionDetailFragment();

final SessionDetailModel sessionDetailModel =

mock(SessionDetailModel.class);

when(sessionDetailModel.isKeynote()).thenReturn(false);

sessionDetailFragment.onActivityCreated(null);
sessionDetailFragment.displayData(sessionDetailModel,

SessionDetailModel.SessionDetailQueryEnum.SESSIONS);

final View addScheduleButton =

sessionDetailFragment.getView()

.findViewById(R.id.add_schedule_button);
assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);

}
@Test

public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()

throws Exception {

SessionDetailFragment sessionDetailFragment =

new SessionDetailFragment();

final SessionDetailModel sessionDetailModel =

mock(SessionDetailModel.class);

when(sessionDetailModel.isKeynote()).thenReturn(false);

sessionDetailFragment.onActivityCreated(null);
sessionDetailFragment.onAttach(null);
sessionDetailFragment.displayData(sessionDetailModel,

SessionDetailModel.SessionDetailQueryEnum.SESSIONS);

final View addScheduleButton =

sessionDetailFragment.getView()

.findViewById(R.id.add_schedule_button);
assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);

}
SessionDetailFragment
has low cohesion
TDD Live
–Steve Freeman and Nat Pryce, 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.”
Activities tend to have tight
coupling and poor cohesion
This code is garbage
If Android Tests
Could Talk
https://twitter.com/philosohacker
https://twitter.com/unikeytech

If Android Tests Could Talk

  • 1.
    If Android Tests CouldTalk Matt Dupree
  • 2.
    Their pace hasquickened
  • 3.
    This code isgarbage
  • 4.
    –Steve Freeman andNat Pryce, 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.”
  • 5.
    –Steve Freeman andNat Pryce, 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.”
  • 6.
    –Steve Freeman andNat Pryce, 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.”
  • 7.
    Activities tend tohave tight coupling and poor cohesion
  • 8.
    Application components (activities, services,providers, receivers) are interfaces for your application to interact with the operating system; don’t take them as a recommendation of the facilities you should architect your entire application around. —Chet Haase, Developing for Android VII
  • 9.
    Activities tend tohave tight coupling and poor cohesion
  • 11.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (requestCode != REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 if (mMapFragment != null) {
 mMapFragment.setMyLocationEnabled(true);
 }
 } else {
 // Permission was denied. Display error message.
 Toast.makeText(this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
 super.onRequestPermissionsResult(requestCode, permissions,
 grantResults);
 }
  • 12.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (requestCode != REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 if (mMapFragment != null) {
 mMapFragment.setMyLocationEnabled(true);
 }
 } else {
 // Permission was denied. Display error message.
 Toast.makeText(this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
 super.onRequestPermissionsResult(requestCode, permissions,
 grantResults);
 }
  • 13.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (requestCode != REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 if (mMapFragment != null) {
 mMapFragment.setMyLocationEnabled(true);
 }
 } else {
 // Permission was denied. Display error message.
 Toast.makeText(this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
 super.onRequestPermissionsResult(requestCode, permissions,
 grantResults);
 }
  • 14.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (requestCode != REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 if (mMapFragment != null) {
 mMapFragment.setMyLocationEnabled(true);
 }
 } else {
 // Permission was denied. Display error message.
 Toast.makeText(this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
 super.onRequestPermissionsResult(requestCode, permissions,
 grantResults);
 }
  • 15.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (requestCode != REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 if (mMapFragment != null) {
 mMapFragment.setMyLocationEnabled(true);
 }
 } else {
 // Permission was denied. Display error message.
 Toast.makeText(this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
 super.onRequestPermissionsResult(requestCode, permissions,
 grantResults);
 }
  • 17.
    @Test
 public void showsToastIfPermissionIsRejected()
 throwsException {
 MapActivity mapActivity = new MapActivity();
 
 mapActivity.onRequestPermissionsResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{
 PackageManager.PERMISSION_DENIED});
 
 assertToastDisplayed();
 }
  • 18.
    @Test
 public void showsToastIfPermissionIsRejected()
 throwsException {
 MapActivity mapActivity = new MapActivity();
 
 mapActivity.onRequestPermissionsResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{
 PackageManager.PERMISSION_DENIED});
 
 assertToastDisplayed();
 }
  • 19.
    @Test
 public void showsToastIfPermissionIsRejected()
 throwsException {
 MapActivity mapActivity = new MapActivity();
 
 mapActivity.onRequestPermissionsResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{
 PackageManager.PERMISSION_DENIED});
 
 assertToastDisplayed();
 }
  • 20.
    @Test
 public void showsToastIfPermissionIsRejected()
 throwsException {
 MapActivity mapActivity = new MapActivity();
 
 mapActivity.onRequestPermissionsResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION}, new int[]{
 PackageManager.PERMISSION_DENIED});
 
 assertToastDisplayed();
 }
  • 21.
    private void assertToastDisplayed(){
 // It can't be done, sir 
 }
  • 22.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (requestCode != REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 if (mMapFragment != null) {
 mMapFragment.setMyLocationEnabled(true);
 }
 } else {
 // Permission was denied. Display error message.
 Toast.makeText(this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
 super.onRequestPermissionsResult(requestCode, permissions,
 grantResults);
 }
  • 27.
    MapActivity is tightlycoupled with MapFragment and Toast
  • 28.
    static class OnPermissionResultListener{ 
 private final PermittedView mPermittedView;
 
 void onPermissionResult(final int requestCode,
 final String[] permissions, final int[] grantResults) {}
 
 interface PermittedView { 
 void displayPermissionDenied(); void displayPermittedView();
 }
 }
  • 29.
    static class OnPermissionResultListener{ 
 private final PermittedView mPermittedView;
 
 void onPermissionResult(final int requestCode,
 final String[] permissions, final int[] grantResults) {}
 
 interface PermittedView { 
 void displayPermissionDenied(); void displayPermittedView();
 }
 }
  • 30.
    static class OnPermissionResultListener{ 
 private final PermittedView mPermittedView;
 
 void onPermissionResult(final int requestCode,
 final String[] permissions, final int[] grantResults) {}
 
 interface PermittedView { 
 void displayPermissionDenied(); void displayPermittedView();
 }
 }
  • 31.
    void onPermissionResult(final intrequestCode,
 final String[] permissions, final int[] grantResults) {
 if (requestCode != MapActivity.REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 MapActivity.LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 mPermittedView.displayPermittedView();
 
 } else {
 // Permission was denied. Display error message.
 mPermittedView.displayPermissionDenied();
 }
 }
  • 32.
    void onPermissionResult(final intrequestCode,
 final String[] permissions, final int[] grantResults) {
 if (requestCode != MapActivity.REQUEST_LOCATION_PERMISSION) {
 return;
 }
 
 if (permissions.length == 1 &&
 MapActivity.LOCATION_PERMISSION.equals(permissions[0]) &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 // Permission has been granted.
 mPermittedView.displayPermittedView();
 
 } else {
 // Permission was denied. Display error message.
 mPermittedView.displayPermissionDenied();
 }
 }
  • 33.
    @Test
 public void displaysErrorWhenPermissionRejected()throws Exception {
 
 OnPermissionResultListener onPermissionResultListener =
 new OnPermissionResultListener(mPermittedView);
 
 onPermissionResultListener.onPermissionResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION},
 new int[]{PackageManager.PERMISSION_DENIED});
 
 verify(mPermittedView).displayPermissionDenied();
 }
  • 34.
    @Test
 public void displaysErrorWhenPermissionRejected()throws Exception {
 
 OnPermissionResultListener onPermissionResultListener =
 new OnPermissionResultListener(mPermittedView);
 
 onPermissionResultListener.onPermissionResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION},
 new int[]{PackageManager.PERMISSION_DENIED});
 
 verify(mPermittedView).displayPermissionDenied();
 }
  • 35.
    @Test
 public void displaysErrorWhenPermissionRejected()throws Exception {
 
 OnPermissionResultListener onPermissionResultListener =
 new OnPermissionResultListener(mPermittedView);
 
 onPermissionResultListener.onPermissionResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION},
 new int[]{PackageManager.PERMISSION_DENIED});
 
 verify(mPermittedView).displayPermissionDenied();
 }
  • 36.
    @Test
 public void displaysErrorWhenPermissionRejected()throws Exception {
 
 OnPermissionResultListener onPermissionResultListener =
 new OnPermissionResultListener(mPermittedView);
 
 onPermissionResultListener.onPermissionResult(
 MapActivity.REQUEST_LOCATION_PERMISSION,
 new String[]{MapActivity.LOCATION_PERMISSION},
 new int[]{PackageManager.PERMISSION_DENIED});
 
 verify(mPermittedView).displayPermissionDenied();
 }
  • 37.
  • 38.
    –Edward Yourdon andLarry Constantine “what we are striving for is loosely coupled systems…in which one can study (or debug, or maintain) any one module without having to know very much about any other modules in the system”
  • 40.
    @Override
 public void displayPermissionDenied(){
 Toast.makeText(MapActivity.this, R.string.map_permission_denied,
 Toast.LENGTH_SHORT).show();
 }
  • 41.
    @Override
 public void displayPermissionDenied(){
 PermissionsUtils.displayConditionalPermissionDenialSnackbar(this,
 R.string.map_permission_denied, PERMISSIONS,
 REQUEST_LOCATION_PERMISSION, false);
 }
  • 42.
    –Kent Beck, TDDBy Example “Dependency is the key problem in software development at all scales…if dependency is the problem, duplication is the symptom.”
  • 44.
    @Override
 public void onRequestPermissionsResult(finalint requestCode,
 @NonNull final String[] permissions,
 @NonNull final int[] grantResults) {
 
 if (grantResults.length == APP_REQUIRED_PERMISSIONS.length &&
 grantResults[0] == PackageManager.PERMISSION_GRANTED &&
 grantResults[1] == PackageManager.PERMISSION_GRANTED) {
 // If permission granted then refresh account list so user can
 // select an account.
 clearSnackbar();
 reloadAccounts();
 refreshAccountListUI();
 } else {
 LOGI(TAG, "onRequestPermissionResult with permissions denied");
 mSnackbar = new WeakReference<>(PermissionsUtils
 .displayConditionalPermissionDenialSnackbar(
 getActivity(),
 R.string.welcome_permissions_rationale,
 APP_REQUIRED_PERMISSIONS,
 REQUEST_PERMISSION_REQUEST_CODE));
 }
 }
  • 45.
    Activities tend tohave tight coupling and poor cohesion
  • 46.
    –Steve Freeman andNat Pryce, 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.”
  • 47.
    Activities tend tohave tight coupling and poor cohesion
  • 48.
    –Steve Freeman andNat Pryce, 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.”
  • 50.
    private void displaySessionData(finalSessionDetailModel data) { //46 statement method inside a 900 line class 
 
 // Handle Keynote as a special case, where the user cannot remove it
 // from the schedule (it is auto added to schedule on sync)
 mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&
 !data.isKeynote());
 mAddScheduleFab
 .setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);
 
 if (!data.isKeynote()) {
 showInScheduleDeferred(data.isInSchedule());
 }
 //... 
 updateTimeBasedUi(data);
 
 
 }
  • 51.
    private void displaySessionData(finalSessionDetailModel data) { //46 statement method inside a 900 line class! 
 
 // Handle Keynote as a special case, where the user cannot remove it
 // from the schedule (it is auto added to schedule on sync)
 mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&
 !data.isKeynote());
 mAddScheduleFab
 .setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);
 
 if (!data.isKeynote()) {
 showInScheduleDeferred(data.isInSchedule());
 }
 //... 
 updateTimeBasedUi(data);
 
 
 }
  • 52.
    private void displaySessionData(finalSessionDetailModel data) { //46 statement method inside a 900 line class 
 
 // Handle Keynote as a special case, where the user cannot remove it
 // from the schedule (it is auto added to schedule on sync)
 mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&
 !data.isKeynote());
 mAddScheduleFab
 .setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);
 
 if (!data.isKeynote()) {
 showInScheduleDeferred(data.isInSchedule());
 }
 //... 
 updateTimeBasedUi(data);
 
 
 }
  • 53.
    private void displaySessionData(finalSessionDetailModel data) { //46 statement method inside a 900 line class 
 
 // Handle Keynote as a special case, where the user cannot remove it
 // from the schedule (it is auto added to schedule on sync)
 mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&
 !data.isKeynote());
 mAddScheduleFab
 .setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);
 
 if (!data.isKeynote()) {
 showInScheduleDeferred(data.isInSchedule());
 }
 //... 
 updateTimeBasedUi(data);
 
 
 }
  • 54.
    private void displaySessionData(finalSessionDetailModel data) { //46 statement method inside a 900 line class 
 
 // Handle Keynote as a special case, where the user cannot remove it
 // from the schedule (it is auto added to schedule on sync)
 mShowFab = (AccountUtils.hasActiveAccount(getContext()) &&
 !data.isKeynote());
 mAddScheduleFab
 .setVisibility(mShowFab ? View.VISIBLE : View.INVISIBLE);
 
 if (!data.isKeynote()) {
 showInScheduleDeferred(data.isInSchedule());
 }
 //... 
 updateTimeBasedUi(data);
 
 
 }
  • 56.
    @Test
 public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()
 throwsException {
 SessionDetailFragment sessionDetailFragment =
 new SessionDetailFragment();
 final SessionDetailModel sessionDetailModel =
 mock(SessionDetailModel.class);
 when(sessionDetailModel.isKeynote()).thenReturn(false);
 sessionDetailFragment.displayData(sessionDetailModel,
 SessionDetailModel.SessionDetailQueryEnum.SESSIONS);
 final View addScheduleButton =
 sessionDetailFragment.getView()
 .findViewById(R.id.add_schedule_button); assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);
 }
  • 57.
    @Test
 public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()
 throwsException {
 SessionDetailFragment sessionDetailFragment =
 new SessionDetailFragment();
 final SessionDetailModel sessionDetailModel =
 mock(SessionDetailModel.class);
 when(sessionDetailModel.isKeynote()).thenReturn(false);
 sessionDetailFragment.displayData(sessionDetailModel,
 SessionDetailModel.SessionDetailQueryEnum.SESSIONS);
 final View addScheduleButton =
 sessionDetailFragment.getView()
 .findViewById(R.id.add_schedule_button); assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);
 }
  • 58.
    @Test
 public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()
 throwsException {
 SessionDetailFragment sessionDetailFragment =
 new SessionDetailFragment();
 final SessionDetailModel sessionDetailModel =
 mock(SessionDetailModel.class);
 when(sessionDetailModel.isKeynote()).thenReturn(false);
 sessionDetailFragment.displayData(sessionDetailModel,
 SessionDetailModel.SessionDetailQueryEnum.SESSIONS);
 final View addScheduleButton =
 sessionDetailFragment.getView()
 .findViewById(R.id.add_schedule_button); assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);
 }
  • 59.
    @Test
 public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()
 throwsException {
 SessionDetailFragment sessionDetailFragment =
 new SessionDetailFragment();
 final SessionDetailModel sessionDetailModel =
 mock(SessionDetailModel.class);
 when(sessionDetailModel.isKeynote()).thenReturn(false);
 sessionDetailFragment.displayData(sessionDetailModel,
 SessionDetailModel.SessionDetailQueryEnum.SESSIONS);
 final View addScheduleButton =
 sessionDetailFragment.getView()
 .findViewById(R.id.add_schedule_button); assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);
 }
  • 61.
    @Test
 public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()
 throwsException {
 SessionDetailFragment sessionDetailFragment =
 new SessionDetailFragment();
 final SessionDetailModel sessionDetailModel =
 mock(SessionDetailModel.class);
 when(sessionDetailModel.isKeynote()).thenReturn(false);
 sessionDetailFragment.onActivityCreated(null); sessionDetailFragment.displayData(sessionDetailModel,
 SessionDetailModel.SessionDetailQueryEnum.SESSIONS);
 final View addScheduleButton =
 sessionDetailFragment.getView()
 .findViewById(R.id.add_schedule_button); assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);
 }
  • 62.
    @Test
 public void onlyProvidesAddSessionToggleIfSessionIsNotKeynote()
 throwsException {
 SessionDetailFragment sessionDetailFragment =
 new SessionDetailFragment();
 final SessionDetailModel sessionDetailModel =
 mock(SessionDetailModel.class);
 when(sessionDetailModel.isKeynote()).thenReturn(false);
 sessionDetailFragment.onActivityCreated(null); sessionDetailFragment.onAttach(null); sessionDetailFragment.displayData(sessionDetailModel,
 SessionDetailModel.SessionDetailQueryEnum.SESSIONS);
 final View addScheduleButton =
 sessionDetailFragment.getView()
 .findViewById(R.id.add_schedule_button); assertTrue(addScheduleButton.getVisibility() == View.INVISIBLE);
 }
  • 63.
  • 64.
  • 65.
    –Steve Freeman andNat Pryce, 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.”
  • 66.
    Activities tend tohave tight coupling and poor cohesion
  • 67.
    This code isgarbage
  • 68.
    If Android Tests CouldTalk https://twitter.com/philosohacker https://twitter.com/unikeytech