droidconuk: tdd android with robolectric
DESCRIPTION
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.TRANSCRIPT
Android Unit Test Frameworkhttp://robolectric.org
@robolectric
Saturday, October 8, 11
Joe Moore@joem
Pivotal Labs@pivotallabs
Saturday, October 8, 11
Disclaimer
I’m mostly a Ruby guy now
Saturday, October 8, 11
Agenda
• Testing Approaches and Alternatives
• How Robolectric works
• How to extend Robolectric
Saturday, October 8, 11
Software consulting company, primarily Ruby on Rails, Android, and iOS
XP, TDD, Pair Programming
SF, NYC, Boulder CO, SingaporeSaturday, October 8, 11
BDD for iOSCedar
JsUnit
We Test StuffGreat Expectations
Jasmine-style assertions for JUnit
Saturday, October 8, 11
java.lang.RuntimeException(“Stub!”)
Saturday, October 8, 11
Google replaced the method bodies in android.jar with:
java.lang.RuntimeException(“Stub!”)
Saturday, October 8, 11
java.lang.RuntimeException: Stub!! at android.content.Context.<init>(Context.java:4)! at android.content.ContextWrapper.<init>(ContextWrapper.java:5)! at android.view.ContextThemeWrapper.<init>(ContextThemeWrapper.java:5)! at android.app.Activity.<init>(Activity.java:6)! at com.pivotallabs.NamesActivity.<init>(NamesActivity.java:9)
Every JUnit Test Fails
Saturday, October 8, 11
Additional Android Testing Challenges
• Many of the classes, methods are final
• Lack of interfaces
• Non public constructors
• Static methods
Saturday, October 8, 11
It’s Getting Better
CalculonA testing DSL for Google Android
Saturday, October 8, 11
How have you been testing?
Saturday, October 8, 11
Android Testing Approaches
Saturday, October 8, 11
Android Testing Approaches
• Integration-style
Saturday, October 8, 11
Android Testing Approaches
• Integration-style
•Library of tested POJO’s
Saturday, October 8, 11
Android Testing Approaches
• Integration-style
•Library of tested POJO’s
•Mocking framework
Saturday, October 8, 11
Android Testing Approaches
• Integration-style
•Library of tested POJO’s
•Mocking framework
•F@*K IT!
Saturday, October 8, 11
Why use Robolectric?
Saturday, October 8, 11
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
Instrumentation Tests require dexing, packaging and installation on an emulator or device to run in the Dalvik VM.
vs. Android Instrumentation Tests?Instrumentation Tests are
Saturday, October 8, 11
Robolectric runs in the regular JVM:
No dexing, packaging, deploying, etc.
vs. Android Instrumentation Tests?
FAST
Saturday, October 8, 11
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
• http://robolectric.org
• http://github.com/pivotal/robolectric/
• http://github.com/pivotal/RobolectricSample
How can I get started?
Saturday, October 8, 11
Writing Tests
Saturday, October 8, 11
Writing TestsTests that reference Android need to be annotated:
@RunWith(RobolectricTestRunner.class)public class MyActivityTest {
@Test! public void shouldDoWizbangFooBar() {...
Saturday, October 8, 11
Writing TestsSometimes you do not even see Robolectric
(Where is Robolectric? Nice and hidden!)
@RunWith(RobolectricTestRunner.class)public class MyActivityTest { private View homeButton; private Activity activity ...
@Test! public void shouldDoWizbangFooBar() { homeButton = activity.findViewById(R.id.home);
Saturday, October 8, 11
...
@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));
}
Writing TestsTalking to Shadows
Saturday, October 8, 11
HTTP Testing
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=28...
Unexpected HTTP Calls Throw Exceptions
Saturday, October 8, 11
HTTP TestingHTTP Handling Methods
public class Robolectric
Saturday, October 8, 11
HTTP Testing
new FakeHttpLayer.RequestMatcherBuilder() .host("http://example.com") .path("foo/bar.json") .method("GET") .param("page","28") .header("header1", "someValue");
Robolectric.addHttpResponseRule(requestMatcher, response)
Saturday, October 8, 11
HTTP TestingRobolectric.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
HighlightsGetting the Latest...
ShadowAlertDialog.getLatestDialog()ShadowAlertDialog.getLatestAlertDialog()ShadowToast.getLatestToast()
myShadowNotification.getLatestEventInfo()
Robolectric.getShadowApplication().getNextStartedActivity()~ or ~ .getNextStartedService()
Saturday, October 8, 11
HighlightsLoopers, Schedulers
ShadowLooper.getMainLooper()
shadowMainLooper.getScheduler()
Robolectric.pauseMainLooper()
Robolectric.unPauseMainLooper()
Saturday, October 8, 11
How does it work?
Saturday, October 8, 11
Shadow Objects
“Shadow” implementations of Android classes
Saturday, October 8, 11
Shadow Objects
Saturday, October 8, 11
Button(Android)
ShadowButtonmyButton.getText() getText()
text=“Okay”return “Okay”return “Okay”
Shadow Objects
Saturday, October 8, 11
Button(Android)
ShadowButton
myButton.getSomething() getSomething()
Does not implement getSomething()
return null;
Shadow Objects
Saturday, October 8, 11
How does it work?1. Robolectric intercepts the loading of Android
classes under test
Saturday, October 8, 11
How does it work?1. Robolectric intercepts the loading of Android
classes under test
2. Rewrites the method bodies of Android classes (using javassist)
Shadow Objects
Saturday, October 8, 11
How does it work?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
Shadow Objects
Saturday, October 8, 11
How does it work?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 objects
Shadow Objects
Saturday, October 8, 11
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
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/robolectric
Saturday, October 8, 11
Extending Robolectric
Help Robolectric cover moreof Android
Saturday, October 8, 11
Shadow Objects
Shadow inheritance works.
Some shadows are not implemented because most of their functionality is inherited.
Saturday, October 8, 11
Writing Shadow Objects
• @Implements
• @Implementation
• Robolectric.getDefaultShadowClasses()
• __constructor__
• @RealObject
Saturday, October 8, 11
Shadow Objects@Implements
@Implements(View.class)public class ShadowView { ...}
Saturday, October 8, 11
Shadow Objects@Implementation
... @Implementation public int getId() { return this.id; } ...
Saturday, October 8, 11
Shadow ObjectsRobolectric.getDefaultShadowClasses()
return Arrays.asList( ShadowAbsListView.class, ShadowAbsoluteLayout.class, ShadowAbsSeekBar.class, ShadowAbsSpinner.class, ShadowAbstractCursor.class, ShadowActivity.class, ShadowActivityInfo.class, ...
Saturday, October 8, 11
Shadow Objects__constructor__
public class View { public View(Context context) { /* compiled code */ } ...
public class ShadowView { public void __constructor__(Context context) { ... } ...
Saturday, October 8, 11
Shadow Objects@RealObject
@Implements(View.class)public class ShadowView { @RealObject private View realView; ...
Saturday, October 8, 11
Joe Moore@joem
Pivotal Labs@pivotallabs
Thanks!
Saturday, October 8, 11