Android Unit Test Framework
http://pivotal.github.com/robolectricFollow us on twitter: @robolectric
Tuesday, March 22, 2011
Joe Moore@joem
Pivotal Labs@pivotallabs
Tuesday, March 22, 2011
Talking Points
• Testing Approaches and Alternatives
• How Robolectric works
• How to extend Robolectric
• Workshop - write tests & help getting you setup
Tuesday, March 22, 2011
Disclaimer
• I’m more of a Ruby guy now.
• My Java Fu is lacking!
Tuesday, March 22, 2011
Pivotal Labs
• www.pivotallabs.com
• San Francisco (Headquarters), New York, Boulder, Singapore
• Primarily Rails - we do mobile too!
• Agile, XP, Continuos Integration, Pair Programming
Tuesday, March 22, 2011
Pivotal Labs
• Jasmine - Javascript BDD test framework, @jasminebdd
• Cedar - iOS/Objective-C BDD test framework, @cedarbdd
• Pivotal Tracker - www.pivotaltracker.com
You may have heard of us:
Tuesday, March 22, 2011
java.lang.RuntimeException(“Stub!”)
Tuesday, March 22, 2011
Google has stripped the classes in the android.jar file and
have had all their method bodies replaced with:
java.lang.RuntimeException(“Stub!”)
Tuesday, March 22, 2011
Additional Android testing challenges
• Many of the classes and methods are final
• Lack of interfaces
• Non public constructors
• static methods
Tuesday, March 22, 2011
How have you been testing?
Tuesday, March 22, 2011
Android Testing Approaches
Tuesday, March 22, 2011
Android Testing Approaches
• No Tests! EGAD!
Tuesday, March 22, 2011
Android Testing Approaches
• No Tests! EGAD!
• Android InstrumentationTests/Robotium - integration style testing of Android apps
Tuesday, March 22, 2011
Android Testing Approaches
• No Tests! EGAD!
• Android InstrumentationTests/Robotium - integration style testing of Android apps
• Library of tested POJO’s, referenced from a non tested Android project
Tuesday, March 22, 2011
Android Testing Approaches
• No Tests! EGAD!
• Android InstrumentationTests/Robotium - integration style testing of Android apps
• Library of tested POJO’s, referenced from a non tested Android project
• Mocking framework such as Easy Mock and Mockito
Tuesday, March 22, 2011
It’s Getting Better
• Robotium
• AndroidMock
• Calculon
• Robolectric
Tuesday, March 22, 2011
Robolectric
Why use Robolectric?What makes it so great?
Tuesday, March 22, 2011
Why Use Robolectricvs. Android Instrumentation Tests?
• Run FAST in a normal JVM, not on the emulator in the Dalvik VM (except for that damn regression...)
- Running in a Dalvik VM requires dexing, packaging and installation on an emulator or device - slow!
- Tests execute quickly in the JVM and execute slowly on the emulator
Tuesday, March 22, 2011
Why Use Robolectricvs. Android Instrumentation Tests?
• Iterate quickly!
• The latest Pivotal Android project is using Robolectric boasting 1,047 tests that run in 28 seconds!
Tuesday, March 22, 2011
Why Use Robolectricvs. POJO lib approach?
• We used to do this.
• The POJO lib approach leads to code proliferation, interfaces with multiple implementations - code bloat!
• Robolectric allows for vastly increased test coverage. Test ALL your code, not just non-Android code.
Tuesday, March 22, 2011
• Mocking frameworks can lead to tests that are reverse implementation of the code
• Can lead to tests that are hard to read
• Can lead to tests that don’t help refactoring
Why use Robolectricvs. Mock approach?
Tuesday, March 22, 2011
Why Use Robolectric?
• Iterate quickly
• Robolectric allows for a black box style of testing
• Test behavior instead of implementation
• High test coverage
Tuesday, March 22, 2011
How does it work?
• Shadow Objects
• View and Resource Loading
Tuesday, March 22, 2011
How does it work?
• Robolectric intercepts the loading of Android classes under test
• Rewrites the method bodies of Android classes (using javassist)
• Binds new shadow objects to new Android objects
• The modified Android objects proxy method calls to the shadow objects
Shadow objects
Tuesday, March 22, 2011
How does it work?
• Shadows back the Android classes. i.e. ShadowImageView backs the ImageView class.
• AndroidObject.someMethod() => proxies to => ShadowAndroidObject.someMethod()
• State is recorded so it can be verified in tests
Shadow objects
Tuesday, March 22, 2011
How does it work?
• Robolectric parses layout files and builds a view object tree made of Android view objects and, of course, their shadows.
• Some of the view xml attributes are applied to the view object
• Strings, string arrays, and color resources are parsed loaded too.
View and Resource Loading
Tuesday, March 22, 2011
• http://pivotal.github.com/robolectric/index.html
• http://github.com/pivotal/RobolectricSample
How can I get started?
Tuesday, March 22, 2011
Robolectric IDE support
• We are IntelliJ guys
• Eclipse works, too
• Maven!
Tuesday, March 22, 2011
Robolectric
Writing Tests
Tuesday, March 22, 2011
Writing Tests
...@RunWith(RobolectricTestRunner.class)public class MyActivityTest {
@Test! public void shouldDoWizbangFooBar() {...
Tests that reference Android need to be annotated:
Tuesday, March 22, 2011
Writing Tests@Testpublic void shouldShowLogoWhenButtonIsPressed() {
Activity activity = new MyActivity();activity.onCreate(null);ImageView logo = (ImageView) activity.findViewById(R.id.logo);Button button = (Button) activity.findViewById(R.id.button);
assertThat(logo.getVisibility(), equalTo(View.GONE));
button.performClick();
assertThat(logo.getVisibility(), equalTo(View.VISIBLE));}
Tuesday, March 22, 2011
Writing Tests
Dealing with cases where Android classes do not provide a way to retrieve object state
Tuesday, March 22, 2011
Writing TestsAccessing the Shadow Object
<ImageViewandroid:id=”@+id/logo”android:layout_width=”wrap_content”android:layout_height=”wrap_content”android:src=”@drawable/logo” />
...
@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));
}
Tuesday, March 22, 2011
Shadow Objects
• @RealObject
• __constructor__
• @Implements
• @Implementation
• Robolectric.bindAllShadowClasses()
Tuesday, March 22, 2011
Shadow Objects@RealObject
• Robolectric is using reflection to instantiate the shadow object (default or no-args constructor)
• Robolectric will inject the Android object onto shadow object’s fields annotated with @RealObject
Tuesday, March 22, 2011
Shadow Objects@RealObject
@Implements(View.class)public class ShadowView {
@RealObject private View realView; private int id;...
Tuesday, March 22, 2011
Shadow Objects
• If no shadow class is registered for an Android class, the Android object’s super constructor will seek out a shadow class, up through the constructor super chain until one is found.
Tuesday, March 22, 2011
Shadow Objects__constructor__
• When Robolectric is finished instantiating the shadow object, it will attempt to invoke a method on the shadow named __constructor__ that has the same args as the Android object’s constructor
Tuesday, March 22, 2011
Shadow Objects__constructor__
public class Intent { public Intent(String action, Uri uri) { /* compiled code */ } ...}
public class ShadowIntent { public void __constructor__(String action, Uri uri) { ... } ...}
Tuesday, March 22, 2011
Shadow Objects@Implements
@Implements(View.class)public class ShadowView {
@RealObject private View realView; private int id;...
Tuesday, March 22, 2011
Shadow Objects@Implementation
public class ShadowTextView {... @Implementation public CharSequence getText() { return text; }...
Tuesday, March 22, 2011
Shadow ObjectsRobolectric.bindAllShadowClasses()
• Where shadow objects are registered into Robolectric
• This is a current listing of all the shadow objects provided by Robolectric
Tuesday, March 22, 2011
Joe Moore@joem
Pivotal Labs@pivotallabs
Thanks!
Tuesday, March 22, 2011