Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

Tuesday, August 7, 2012

Android Unit Testing With Robolectric

Are you interested in adding Robolectric to your development process? Here's a quick introduction to the most common types of test cases and some best practices to consider when deciding your testing strategy.

I've created two example apps for your reference. The first is a glorified Hello World and the other is a simple animation app. If you are unfamiliar with Robolectric, I'd start with my introductory blog that details how to integrate IntelliJ, Robolectric, and Maven into your project.

What To Test

You should focus your unit testing on business logic, basic view validation, and functionality. You can validate certain requirements very well with unit testing (e.g. "when I click this bell image, it whistles" or "all activities have a logo on the screen").

Here are some common examples:
  • Ensure the activity and view elements are not null.
  • Ensure expected view elements are visibile on the screen.
  • Ensure that the proper resources are used for view elements.
  • Ensure items are hidden when expected.
  • Ensure that your intent starts the correct thing.
  • Ensure that fragments are present.

What Not To Test

I don't recommend investing much time in verifying less important styling details and XML attributes. Your user interface is likely to change many times a week, but typically the basic view elements and their behavior remain the same. If you updated a test each time a button had new padding, that would get very tedious indeed!

Example: The app logo is 250dp high with a proportional width, the file name is "main_logo.png", it has a 20dp left margin and 12dp right margin, has a content description of "Text," and is centered vertically on every screen.

You are likely able to test all of the attributes above (I haven't tried), but your time is best spent on items that are most important to the business. From this example, likely the most bang for your buck will come from testing that the proper image is used and making sure that it appears on all expected screens.

Returns start diminishing pretty quickly when you verify every possible XML attribute - unless that detail has an important business outcome for your app. Many of the attributes you don't test can be caught by designers, quality assurance, business, product, and/or the engineers. A better stategy is to sweep the app near deployment with a designer to make sure the app appears as expected on a wide range of devices.

Best Practices

  • Simplify your architecture. As with most object oriented realms, attempt to simplify the architecture as much as possible. Do this by creating abstract or base objects or interfaces. This way you can test shared behavior in one place instead of several, which simplifies tests and reduces code duplication.
  • Mock/stub functionality. Often we want to ensure that a certain action was performed but not actually run the code. To do this, you create a test class which extends the class under test and override/mock out/track that a particular function was called. For example, if you want a particular click to make an API call, there is no need to make the actual call (since this would be an integration level test). You can use this strategy to track that the function that invokes the call has been called, but avoid making the actual call. This is also helpful when an operation crashes the unit testing framework (e.g. specialty library that causes funky side effects), doesn't play nice, or is otherwise unnecessary to invoke directly.
  • Create a custom test runner. Create a helper test runner to gives your entire unit test suite access to handy functionality. My simplest version includes string, drawable, and color resource fetching as well as view visibility helper functions. I also add a helper function to start fragments without a hosting activity. Finally, this is a good place to bind custom shadows you create and set up any external library configurations that you may need.
  • Centralize test constants. Android strongly encourages the use of abstracting string resources into one place over using hard-coded string constants. Use a test constants class when you have lots of test data (e.g. When creating a user with lots of fields to mock out a JSON response).
  • Have a robust testing strategy. Don't rely solely on unit tests to ensure the proper implementation of your app. These tests, though quite useful, only give you a small window into your app and it's behavior. At minimum, you should include integration level tests and have some level of manual QA in the process to ensure your app is everything that you want it to be.

Notes

  • Unit testing Android apps is not as simple as with other platforms and Robolectric is still a work in progress. You will find things that you'd like to test that the library doesn't suport. It's up to your organization to determine how much they would like to invest to give back the the community by writing and fleshing out shadow classes.
  • When running the sample tests you may notice a few warnings about binding map-related shadow classes (because the library is not available in the maven central repository). To solve this issue use the maven android SDK deployer.
    Warning: an error occurred while binding shadow class: ShadowGeoPoint
    Warning: an error occurred while binding shadow class: ShadowItemizedOverlay
    Warning: an error occurred while binding shadow class: ShadowMapController
    Warning: an error occurred while binding shadow class: ShadowMapActivity
    Warning: an error occurred while binding shadow class: ShadowMapView
    Warning: an error occurred while binding shadow class: ShadowOverlayItem
I hope you found this blog useful for getting started with Robolectric! If you are already using it, what do you find most useful or helpful? How does it fit into your testing strategy?

Friday, August 3, 2012

Integrating Robolectric with IntelliJ

Unit testing is a best practice, but has been particularly difficult in Android. One library that makes this process much simpler is Robolectric. Integration into your project can be tricky and most methods start with maven and create an Android project from scratch.

I'm a huge fan of living in IDE-land as much as possible, so this approach minimizes command line work and manual project configuration. This tutorial will walk you through using IntelliJ to generate the project and maven integration to make installation of Robolectric simpler.
After creating an Android project with the wizard, you move the source files into a mvn-like structure. Then you configure module settings in IntelliJ. To install Robolectric, you will add a pom file (maven dependency file) into the project. Finally, you will write your first test and run it inside the IDE.

Note: There are other projects and methods out there, but this is what I found to be the most effective way to get up and running. If you'd prefer an all in one solution, check out this project.

Assumptions:

  • You are starting a new project.
  • You are familiar with the IntelliJ and the Android wizard (CE edition is fine).
  • You have installed the Android SDK and configured IntelliJ to use it.
  • You have maven installed.

Step 1: Create Android project using IntelliJ wizard

Use the IntelliJ Android wizard to create a Hello World project.

Step 2: Set up directories for maven

Use IntelliJ or the command line to move files into the following structure:

src/main/java/com/...
src/test/java/com/...

After you are done, your project should look something like this (minus the test files):
Note: IntelliJ might not like it when you add subdirectories to the folder marked as source. If you are having trouble setting up the directory structure, open the screen shown in the next step and remove the old source folder before adding new subfolders.

Step 3: Configure IntelliJ source and test folders

Update module settings in IntelliJ to recognize the source and test directories (below: blue folder is source and test folder is green).


Step 4: Install Robolectric

Use this sample pom.xml to get you started. Change the groupId, artifactId, version, and name tags to suit your project. The file should live in the root of the project.
And some of my environment set up (zsh):
# Shell Settings (one long line, broken for view online)
export ANDROID_HOME="/usr/bin/android-sdk-macosx"
export PATH="/usr/bin/android-sdk-macosx/platform-tools:/usr/bin:
             /usr/bin/android-sdk-macosx/tools:
             /usr/local/apache-maven-2.1.2/bin:$PATH"
After updating the pom and environment variables, check the project in IntelliJ to make sure the "Maven Projects" view looks something like this (you may need to refresh):


If you see errors, you may just need to wait for it to finish indexing and importing your libraries. If you have git configured, once you see this dialog, you should be moments away from running your first test.

Step 5: Create test

I use the IDE to generate a test. On a Mac, the key combination is Apple+Shift+T.

Step 6: Write test & run

Right click on the test file name to run the test or use keyboard shortcut. TODO - resolve - If you are seeing issues similar to this, rebuild your project a few times to convince IntelliJ to generate the R.java file (which links to all your applications resources) and/or run "mvn install" from the command line.

If you are still having issues, update your Android facet here:
Success!

Optional Step 7: Create a run config for all tests

You could run each test individually as you work with new classes, however it should be a common task to run all the unit tests (e.g. prior to commit). I create a run configuration for the whole project:  


Next Steps

Now that you've got it integrated into your project, why not try adding a few tests? Check out the next blog in the series that shows you how to write test for your project.