Monthly Archives: October 2013

Droid Units

JUnit testing of model and service classes under Android/Dalvik is straightforward, but when it comes to testing UIs (activities and fragments), the basic support is just too low-level. Fortunately, Robotium (think Selenium for droids) provides a most-excellent adjunct for this.

Yet Robotium should be used with care to create maintainable tests that aren’t brittle. To that end, I’ve developed some UI automation practices:

  • Write true unit test classes that cover only a single activity or fragment. Of course, many UI actions will take you to the next activity (and you should assert that), but leave multi-activity testing for separate integration tests.
  • Stub back-end behaviors using standard mock and dependency injection techniques. For my current app, I kept it simple/lightweight and wrote my own code, but I’ve also used Mockito (with dexmaker) and Dagger (with javax.inject); these are nice Android-compatible frameworks.
  • Rather than repeating the same raw Robotium calls directly from your tests, wrap them in descriptive helper methods like enterUserID(String id), enterPassword(String password), clickLoginButton(), etc. This DRY approach makes for more readable tests and simplifies updates when your UI changes.
  • Since you probably use common superclasses for your activities and fragments, also create parent test case classes to factor common testing behaviors. See below for snippets from one of mine.

I haven’t found a good tool for measuring code coverage for apps (Emma under Android is flakely), so I’d love to hear your recommendations.

public abstract class BaseActivityTest<T extends Activity> 
                             extends ActivityInstrumentationTestCase2<T> {
 
	protected Solo mSolo;	
 
	public BaseActivityTest(Class<T> activityClass) {
		super(activityClass);
	}
 
	protected void setUp() throws Exception {
		// Reset your DI container and common mocks here...		
		mSolo = new Solo(getInstrumentation(), getActivity());
	}
 
	protected void tearDown() throws Exception {
		mSolo.finishOpenedActivities();
	}
 
	public void testLayoutPortrait() {
		mSolo.setActivityOrientation(Solo.PORTRAIT);
		verifyViews(getLayoutViews());		
		verifyViews(getPortraitViews());
	}
 
	public void testLayoutLandscape() {
		mSolo.setActivityOrientation(Solo.LANDSCAPE);
		verifyViews(getLayoutViews());		
		verifyViews(getLandscapeViews());		
	}	
 
	// ...
}