DroidconUK: TDD Android with Robolectric

6,803 views

Published on

I gave this presentation at DroidconUK on October 7, 2011 to a room full of amazing Android developers. Time was short, and thus this presentation was shorter than I usually give, with no live TDD coding.

Published in: Technology
  • shadowIntent.getComponent() is null
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

DroidconUK: TDD Android with Robolectric

  1. 1. Android Unit Test Framework http://robolectric.org @robolectricSaturday, October 8, 11
  2. 2. Joe Moore @joem Pivotal Labs @pivotallabsSaturday, October 8, 11
  3. 3. Disclaimer I’m mostly a Ruby guy nowSaturday, October 8, 11
  4. 4. Agenda • Testing Approaches and Alternatives • How Robolectric works • How to extend RobolectricSaturday, October 8, 11
  5. 5. Software consulting company, primarily Ruby on Rails, Android, and iOS XP, TDD, Pair Programming SF, NYC, Boulder CO, SingaporeSaturday, October 8, 11
  6. 6. We Test Stuff Great Expectations Jasmine-style assertions for JUnit Cedar BDD for iOS JsUnitSaturday, October 8, 11
  7. 7. java.lang.RuntimeException(“Stub!”)Saturday, October 8, 11
  8. 8. Google replaced the method bodies in android.jar with:java.lang.RuntimeException(“Stub!”)Saturday, October 8, 11
  9. 9. Every JUnit Test Failsjava.lang.RuntimeException: Stub!! at android.content.Context.<init>(Context.java:4)! at android.content.ContextWrapper.<init>(ContextWra! atandroid.view.ContextThemeWrapper.<init>(ContextThemeWr5)! at android.app.Activity.<init>(Activity.java:6)! at com.pivotallabs.NamesActivity.<init>(NamesActiviSaturday, October 8, 11
  10. 10. Additional Android Testing Challenges • Many of the classes, methods are final • Lack of interfaces • Non public constructors • Static methodsSaturday, October 8, 11
  11. 11. It’s Getting Better Calculon A testing DSL for Google AndroidSaturday, October 8, 11
  12. 12. How have you been testing?Saturday, October 8, 11
  13. 13. Android Testing ApproachesSaturday, October 8, 11
  14. 14. Android Testing Approaches • Integration-styleSaturday, October 8, 11
  15. 15. Android Testing Approaches • Integration-style • Library of tested POJO’sSaturday, October 8, 11
  16. 16. Android Testing Approaches • Integration-style • Library of tested POJO’s • Mocking frameworkSaturday, October 8, 11
  17. 17. Android Testing Approaches • Integration-style • Library of tested POJO’s • Mocking framework • F@*K IT!Saturday, October 8, 11
  18. 18. Why use Robolectric?Saturday, October 8, 11
  19. 19. Features • FAST! • Interact with Android code. • Parsing, loading of layouts, strings.xml, colors.xml, etc. • State tracking of many Android objects • Amazing HTTP/API testing • Wonderful static helpers methods allowing instantiation of privates and other... stuff :) • Supports Android frameworks like RoboGuice, android-mock, and other Android frameworks.Saturday, October 8, 11
  20. 20. vs. Android Instrumentation Tests? Instrumentation Tests are Instrumentation Tests require dexing, packaging and installation on an emulator or device to run in the Dalvik VM.Saturday, October 8, 11
  21. 21. vs. Android Instrumentation Tests? Robolectric runs in the regular JVM: FAST No dexing, packaging, deploying, etc.Saturday, October 8, 11
  22. 22. Robolectric lets you: • Use Ye Olde JUnit • Iterate quickly • Test behavior instead of implementation • Have high test coverage • Supports the way Android developers are taught to develop (for better or for worse.)Saturday, October 8, 11
  23. 23. How can I get started? • http://robolectric.org • http://github.com/pivotal/robolectric/ • http://github.com/pivotal/RobolectricSampleSaturday, October 8, 11
  24. 24. Writing TestsSaturday, October 8, 11
  25. 25. Writing Tests Tests that reference Android need to be annotated: @RunWith(RobolectricTestRunner.class) public class MyActivityTest { @Test ! public void shouldDoWizbangFooBar() { ...Saturday, October 8, 11
  26. 26. Writing Tests Sometimes you do not even see Robolectric@RunWith(RobolectricTestRunner.class)public class MyActivityTest { private View homeButton; private Activity activity ... @Test! public void shouldDoWizbangFooBar() { homeButton = activity.findViewById(R.id.home); (Where is Robolectric? Nice and hidden!)Saturday, October 8, 11
  27. 27. Writing Tests Talking to Shadows...@Testpublic void logoImageViewShouldUseTheLogoDrawable() { ImageView logo = (ImageView) activity.findViewById(R.id.logo); // imageView only provides logo.getDrawable(); ShadowImageView logoShadow = Robolectric.shadowOf(logo); assertThat(logoShadow.resourceId, equalTo(R.drawable.logo));}Saturday, October 8, 11
  28. 28. HTTP Testing Unexpected HTTP Calls Throw Exceptions java.lang.RuntimeException: Unexpected call to execute, no pending responses are available. See Robolectric.addPendingResponse(). Request was: GET http://example.com/foo/bar.json?page= ...Saturday, October 8, 11
  29. 29. HTTP Testing HTTP Handling Methods public class RobolectricSaturday, October 8, 11
  30. 30. HTTP Testing Robolectric.addHttpResponseRule(requestMatcher, response) new FakeHttpLayer.RequestMatcherBuilder() .host("http://example.com") .path("foo/bar.json") .method("GET") .param("page","28") .header("header1", "someValue");Saturday, October 8, 11
  31. 31. HTTP Testing Robolectric.addHttpResponseRule(requestMatcher, response)new TestHttpResponse(200, "{ jsonObj: {key: value}");new TestHttpResponse(200, Fixtures.load(“users.json”); Verifying that your app can parse API data is a good idea!Saturday, October 8, 11
  32. 32. Highlights Getting the Latest... ShadowAlertDialog.getLatestDialog() ShadowAlertDialog.getLatestAlertDialog() ShadowToast.getLatestToast() myShadowNotification.getLatestEventInfo() Robolectric.getShadowApplication() .getNextStartedActivity() ~ or ~ .getNextStartedService()Saturday, October 8, 11
  33. 33. Highlights Loopers, Schedulers ShadowLooper.getMainLooper() shadowMainLooper.getScheduler() Robolectric.pauseMainLooper() Robolectric.unPauseMainLooper()Saturday, October 8, 11
  34. 34. How does it work?Saturday, October 8, 11
  35. 35. Shadow Objects “Shadow” implementations of Android classesSaturday, October 8, 11
  36. 36. Shadow ObjectsSaturday, October 8, 11
  37. 37. Shadow Objects Shadow Button getText() myButton.getText() Button (Android) return “Okay” return “Okay” text=“Okay”Saturday, October 8, 11
  38. 38. Shadow Objects Shadow myButton.getSomething() Button getSomething() Button (Android) return null; Does not implement getSomething()Saturday, October 8, 11
  39. 39. How does it work? 1. Robolectric intercepts the loading of Android classes under testSaturday, October 8, 11
  40. 40. How does it work? Shadow Objects 1. Robolectric intercepts the loading of Android classes under test 2. Rewrites the method bodies of Android classes (using javassist)Saturday, October 8, 11
  41. 41. How does it work? Shadow Objects 1. Robolectric intercepts the loading of Android classes under test 2. Rewrites the method bodies of Android classes (using javassist) 3. Binds “shadow objects” to new Android objectsSaturday, October 8, 11
  42. 42. How does it work? Shadow Objects 1. Robolectric intercepts the loading of Android classes under test 2. Rewrites the method bodies of Android classes (using javassist) 3. Binds “shadow objects” to new Android objects 4. The modified Android objects then proxy method calls to the shadow objectsSaturday, October 8, 11
  43. 43. What About Using “Real” Android Jars? • Brach on github: “reviscerated” • Blog article: robolectric.blogspot.com • It’s hard: hand-building jars, native code, indeterminate expectations.Saturday, October 8, 11
  44. 44. Where do the Shadow Implementations come From? Us, and you!This project is open source and maintained by the Robolectric community. Please contribute at http://github.com/pivotal/robolectricSaturday, October 8, 11
  45. 45. Extending Robolectric Help Robolectric cover more of AndroidSaturday, October 8, 11
  46. 46. Shadow Objects Shadow inheritance works. Some shadows are not implemented because most of their functionality is inherited.Saturday, October 8, 11
  47. 47. Writing Shadow Objects • @Implements • @Implementation • Robolectric.getDefaultShadowClasses() • __constructor__ • @RealObjectSaturday, October 8, 11
  48. 48. Shadow Objects @Implements @Implements(View.class) public class ShadowView { ... }Saturday, October 8, 11
  49. 49. Shadow Objects @Implementation ... @Implementation public int getId() { return this.id; } ...Saturday, October 8, 11
  50. 50. Shadow Objects Robolectric.getDefaultShadowClasses() return Arrays.asList( ShadowAbsListView.class, ShadowAbsoluteLayout.class, ShadowAbsSeekBar.class, ShadowAbsSpinner.class, ShadowAbstractCursor.class, ShadowActivity.class, ShadowActivityInfo.class, ...Saturday, October 8, 11
  51. 51. Shadow Objects __constructor__ public class View { public View(Context context) { /* compiled code */ } ... public class ShadowView { public void __constructor__(Context context) { ... } ...Saturday, October 8, 11
  52. 52. Shadow Objects @RealObject @Implements(View.class) public class ShadowView { @RealObject private View realView; ...Saturday, October 8, 11
  53. 53. Thanks! Joe Moore @joem Pivotal Labs @pivotallabsSaturday, October 8, 11

×