How to build rock-solid apps
&	  keep	  100m+	  users	  happy	  Iordanis	  Giannakakis	  	  iordanis_g	  Savvas	  Dalkitsis	  	  @geeky_android	  #qconlondon	  How Shazam works

Some numbers

Happy users

Even developers?

Android testingFaster release cycles

Better code

Cheaperhttp://testdroid.com/testdroid/5851

Easy

A user walks into a bar

Doing it the test driven way
Write	  UI	  test	  	  BDD	  Write	  unit	  test	  	  Implement	  TDD	  Refactor	  Test first• Generally easier• Eliminates external dependencies• Easily repeatable

The Acceptance Tests cycle
Write	  UI	  test	  	  BDD	  Write	  unit	  test	  	  Implement	  TDD	  Refactor	  Acceptance criteria• Given: arrange• When: act• Then: assert

Example
Given a user is near a music venue
And the server always returns a known result
When the user Shazams
Then the user can check-in their discovery

gwen	  given(user).isNear(lexington());	  	  given(server).returns(lustForLife());	  	  	  	  when(user).shazams();	  	  	  then(user).canCheckIn(lustForLife(),	  lexington());	  http://github.com/shazam/gwen

Other libraries for Acceptance Tests• Instrumentation• JUnit 3• Espresso & Robotium• Fork• HamMock Server

The Unit tests cycle
Write	  UI	  test	  	  BDD	  	  Write	  unit	  test	  Implement	  TDD	  Refactor	  What we don’t test

Activities & Fragments

http://github.com/xxv/android-lifecycle

What we don’t test

What we do test
• Presentation logic• Business logic• That’s itThe MVP pattern

Presenter

ModelView

The MVP pattern

Presenter

AndroidModelView

The MVP pattern• Makes presentation logic testable• No need to test “dummy” view
• Avoid Android dependencies

Dependency Injection, Yourself• Breaks hardcoded dependencies• Behaviour vs Implementation• Implementations for test & runtime

Hardcoded dependencies

ClientFeature X

Feature X

Dependency Injection

ClientInjector	  Feature X
InterfaceModel	  public	  interface	  VenueRetriever	  {	  	  	  	  	  void	  findClosestVenue(VenueFoundCallback	  callback);	  	  }	  	  	  public	  class	  NetworkVenueRetriever	  implements	  VenueRetriever	  {	  	  	  	  	  public	  void	  findClosestVenue(VenueFoundCallback	  callback)	  {	  	  	  	  	  	  	  	  //	  Some	  slow	  networking	  	  	  	  	  }	  	  }	  	  	  public	  class	  LocalVenueRetriever	  implements	  VenueRetriever	  {	  	  	  	  	  public	  void	  findClosestVenue(VenueFoundCallback	  callback)	  {	  	  	  	  	  	  	  	  //	  DB	  look-­‐up	  /	  caching	  layer,	  perhaps?	  	  	  	  	  }	  	  }	  Activity
public	  class	  ResultActivity	  extends	  Activity	  implements	  ResultView	  {	  	  	  private	  final	  VenueRetriever	  venueRetriever;	  	  	  private	  ResultPresenter	  resultPresenter;	  	  	  	  public	  ResultActivity()	  {	  	  	  	  	  venueRetriever	  =	  venueRetriever();	  	  	  }	  	  	  	  public	  void	  onCreate(Bundle	  savedInstanceState)	  {	  	  	  	  	  //	  TODO:	  Setup	  layouts	  &	  views	  	  	  	  	  	  Result	  result	  =	  resultToDisplay();	  	  	  	  	  resultPresenter=new	  ResultPresenter(this,	  venueRetriever,	  result);	  	  	  }	  	  	  	  public	  void	  onStart()	  {	  	  	  	  	  resultPresenter.startPresenting();	  	  	  }	  }	  Presenter	  public	  class	  ResultPresenter	  {	  	  	  	  	  	  public	  ResultPresenter(ResultView	  resultView,	  VenueRetriever	  	  	  	  	  	  	  	  	  	  	  	  venueRetriever,	  Result	  result)	  {	  	  	  	  	  	  	  	  this.resultView	  =	  resultView;	  	  	  	  	  	  	  	  this.venueRetriever	  =	  venueRetriever;	  	  	  	  	  	  	  	  this.result	  =	  result;	  	  	  	  	  }	  	  	  	  	  	  public	  void	  startPresenting()	  {	  	  	  	  	  	  	  	  resultView.showResult(result);	  	  	  	  	  	  	  	  venueRetriever.findClosestVenue(new	  VenueFoundCallback()	  {	  	  	  	  	  	  	  	  	  	  	  public	  void	  venueFound(Venue	  venue)	  {	  	  	  	  	  	  	  	  	  	  	  	  	  	  resultView.showCheckInPrompt(venue);	  	  	  	  	  	  	  	  	  	  	  }	  	  	  	  	  	  	  	  });	  	  	  	  	  }	  	  }	  View	  public	  interface	  ResultView	  {	  	  	  	  	  	  void	  showResult(Result	  track);	  	  	  	  	  	  void	  showCheckInPrompt(Venue	  venue);	  	  }	  Activity	  public	  class	  ResultActivity	  extends	  Activity	  implements	  ResultView	  {	  	  	  	  	  	  public	  void	  showResult(Result	  result)	  {	  	  	  	  	  	  	  	  //TODO	  show	  the	  result	  screen	  &	  bind	  result	  data	  	  	  	  	  }	  	  	  	  	  	  public	  void	  showCheckInPrompt(Venue	  venue)	  {	  	  	  	  	  	  	  	  //TODO	  bind	  the	  venue	  with	  check-­‐in	  prompt	  view	  	  	  	  	  }	  	  }	  Other Unit Test technologies• JUnit 4• Robolectricjava.lang.RuntimeException: Stub!

• Hamcrest• JMockTest execution

Continuous Integration

Speed (Hint: slow)

Usual execution

Test	  Suite	  Spoonhttp://github.com/square/spoon

ForkTest	  Suite	  http://github.com/shazam/forkExampleFork

http://github.com/shazam/forkFork• Pooled execution• Infinitely scalable• Current setup 1 test / 2 seconds

http://github.com/shazam/forkFlakiness monitor

ADB Remote

http://github.com/sleekweasel/CgiAdbRemoteIf all else fails

If all else fails

If all else fails

Summary• Testing is easier than you may think• Practice, practice, practice• Toolset is limited but getting better• Ship it!

Questions?