Tuesday, March 5, 2013

Using ActionBarSherlock with Robolectric 2

Hey everybody! You look great today!

We're getting closer to seamless integration with ActionBarSherlock in Robolectric 2, but it's not quite there yet.

To use the ActionBarSherlock library with our 2.0-alpha-2 release, you'll need to 


Tuesday, February 26, 2013

Robolectric 2.0 alpha 2

I'm happy to announce the second alpha version of Robolectric 2!

As before, this release is alpha-quality, and targeted at early adopters. We expect that there'll be bugs. We expect APIs to change before final release. Please use this alpha only if you can deal with some churn.

Big thanks to Square and Pivotal for giving us time to work on this.


Changes since alpha 1:

  • Classes in the android support lib are now fully enabled without the use of shadows.
  • Resource loading is better than ever. Library project support is much more robust.
  • Internally we've switched from Javassist to ASM for the bytecode manipulation that works Robolectric's magic. This and a couple other optimizations give us a huge performance win: one project's test suite here at Square dropped from 163 seconds to 76.
  • We report our Build.VERSION.SDK_INT as 8 (Froyo) for now.
  • Should get along well with ActionBarSherlock.


Changes coming soon:

  • Removing even more shadows (most non-JNI Android classes should work without much shadowing) and enabling more of the SDK.
  • Easier TestRunner setup and configuration.
  • Performance and memory use improvements.
  • Ability to switch Android SDK level on a per-test basis.


Use it!

If you've been using Robolectric 1.x, see my blog post on migrating to Robolectric 2.0.

If you're new to Robolectric, just add this to your pom:
<dependency>
  <groupId>org.robolectric</groupId>
  <artifactId>robolectric</artifactId>
  <version>2.0-alpha-2</version>
</dependency>
Add a @RunWith to your test class and you're ready to go!
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
  ...
}

Caveats:

Currently only android.support.v4's Fragments and LruCache are supported. Support for the core android Fragments and LruCache from later SDK versions will be coming soon. As a workaround, you should be able to use the support versions for now.


Help:

If you find bugs, make an issue on our github project and label it with robolectric-2.

Migrating to Robolectric 2.0

There are several changes in Robolectric 2.0 that will require modifications to existing projects that use the 1.x versions. Here are some quick notes on migrating your Robolectric 1.x-based test suite to 2.0-alpha-2.

Update your pom.xml

Robolectric's maven groupId changed from com.pivotallabs to org.robolectric:
  <dependency>
    <groupId>org.robolectric</groupId>
    <artifactId>robolectric</artifactId>
    <version>2.0-alpha-2</version>
  </dependency>

Package rename

Robolectric moved from com.xtremelabs.robolectric to org.robolectric. A simple search and replace in your code should do the trick:

$ cd my-project-dir
$ find . -name \*.java | xargs -n 10 \
  sed -i.bak 's/com\.xtremelabs\.robolectric/org.robolectric/g'


Update your TestRunners

If you've subclassed RobolectricTestRunner, you'll need to remove some constructors and override some different methods than before. RobolectricTestRunner and related configuration will be overhauled significantly for 2.0-alpha-3, so if you're doing extensive customization you might want to wait until then to upgrade.

If that doesn't scare you off, here's a template for customizing the test runner in alpha-2:

public class MyTestRunner extends RobolectricTestRunner {
  public MyTestRunner(Class<?> testClass) throws InitializationError {
    super(RobolectricContext.bootstrap(MyTestRunner.class, testClass,
        new RobolectricContext.Factory() {
          @Override
          public RobolectricContext create() {
            return new RobolectricContext() {
              // override methods here to customize things...
            };
          }
        }));
  }
}

Specifying qualifiers

In Robolectric 1.x, there were a few ways to goad it into loading resources other than the defaults, including calling setLayoutQualifierSearchPath() on a ViewLoader (and occasionally it worked). In Robolectric 2, the easiest way is to put a @Values annotation on your test:

@Test @Values(qualifiers = "fr-land")
public void shouldShowBucolicFrenchLandscape() throws Exception {
  // ...
}

Wednesday, January 30, 2013

Robolectric 2.0 alpha 1

Heya Android testers! There've been whispers, intimations, and innuendo of a major rev to Robolectric for a while now. We're happy to announce the first alpha version of Robolectric 2. The main goal for 2.0 is to accomplish more realistic Android simulation with much less need for shadow code.

This release is alpha-quality, and targeted at early adopters. We expect that there'll be bugs. We expect APIs to change before final release. Please use this alpha only if you can deal with some churn.

Big thanks to Square for giving us time to work on this.


Changes so far:

  • Using real android fragment manager code from the support lib.
  • More realistic resource loading, including system resources.
  • Much better support for resource qualifiers (e.g. locale, resolution, etc.).
  • Support for library projects.
  • Groundwork to remove many shadows and use actual android sdk code.
  • Moved to org.robolectric package and maven groupId.


Changes coming soon:

  • Removing many more shadows (most non-JNI Android classes should work without much shadowing).
  • Switching from javassist to ASM for faster startup.
  • Easier TestRunner setup and configuration.
  • Performance and memory use improvements.

Use it!


Add this to your pom (note the changed groupId):
<dependency>
  <groupId>org.robolectric</groupId>
  <artifactId>robolectric</artifactId>
  <version>2.0-alpha-1</version>
</dependency>
Add a @RunWith to your test class and you're ready to go!
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
  ...
}

Upgrading:


If you're upgrading from Robolectric 1.x, you'll need to replace any reference to "com.xtremelabs.robolectric" in your project with "org.robolectric".

If you have a custom TestRunner, you'll need to change its configuration somewhat. We're expecting the custom TestRunner API to change soon.


Help:


If you find bugs, make an issue on our github project and label it with robolectric-2.

Thursday, October 25, 2012

Previewing Your Views

Working with complicated view hierarchies in Android can be a pain. Countless times I've found myself in the debugger hunting through LinearLayouts searching for the right RadioButton to toggle, or trying to figure out whether some TextView has gotten added at the right spot.


Robolectric to the rescue!

Hey, here's a thing! Call Robolectric.dump(view) on some view object, and Robolectric will spew some XML to System.out describing that view and its children:

Robolectric.dump(view) (to System.out):
  <AddressLayout>
    <EditText id="id/street"/>
    <EditText id="id/apartment"/>
    <LinearLayout id="id/city_state_row" visibility="GONE">
      <Spinner id="id/city_picker" visibility="GONE"/>
      <EditText id="id/city" visibility="GONE" text="Toronto"/>
      <StateEditor id="id/province" visibility="GONE" text="ON"/>
    </LinearLayout>
    <LinearLayout id="id/postal_country_row">
      <EditText id="id/postal" text="M4Y 1D2"/>
      <TextView id="id/country_picker" text="Canada"/>
    </LinearLayout>
  </AddressLayout>

Another handy method returns any text contained within a view. If multiple TextViews are inside a layout, their text will be appended together:

Robolectric.innerText(view) returns:
  M4Y 1D2 Canada

Note that "Toronto" and "ON" didn't appear because they weren't visible.

Note: you'll need the latest source or the latest maven 1.2-SNAPSHOT.

Friday, August 19, 2011

Using Robolectric with Real Android Jars

Overview

"Why doesn't Robolectric just use the real Android jars?" This one of the most commonly asked questions. The short answer is that it's hard: hard to get the real Android jars, hard to hook them up, hard to deal with native code, hard to know what the heck is going to happen when real code is called, hard to change from the Shadow-way of doing things, hard hard hard.

But, many of you know that we have a branch at http://github.com/pivotal/robolectric named "reviscerated" that backs Robolectric with real Android implementations. This branch has languished due to lack of time and resources.

This week we spent time reviving this branch, merging the master branch into it and getting it working with APK 10/2.3.3 jars.

Robolectric tests pass in both SDK and real-jars mode, as do the tests in RobolectricSample. Now we need you to help us test this code. If you find it valuable then help us fix what's broken.

So, what happens?

All non-Shadowed entities, such as methods and classes, call through to the real Android implementations. No more "Stub!" exceptions from Android, no more null return values or no-op implementations from Robolectric -- if you call Android code, you get Android code. Note that any methods implemented in a Shadow are still called -- shadowed things win.

Essential Android Links:

Terminology:

  • SDK jars: These are the jars installed by android using AVD or command line tools. Implementations replaced with "Stub!" exceptions and entities annotated @hide are either non-public or otherwise unavailable.
  • real jars: Jars generated by manually building Android. Full implementations present including entities annotated @hide.

Getting The Real Jars

Google does not distribute the real Android jars -- you must build them yourself.


Setting up your Workstation

Follow Mac setup instructions here: http://source.android.com/source/initializing.html

After create and mount (double-click) the sparse image, make a directory specifically for the version of Android you want to build.

/Volumes/android $ ls
gingerbread-233 honeycomb-30

Note: once you have built one version of android it is is hard to use that same dir to build another version. I don't know how to clean up from the last version. It's hit-or-miss.

# choose a specific branch found on http://source.android.com/source/build-numbers.html
/Volumes/android/gingerbread-233 $ repo init -u git://android.git.kernel.org/platform/manifest.git -b android-2.3.3_r1
/Volumes/android/gingerbread-233 $ repo sync # give this 40+ minutes

Compiling, Building

See the build instructions here: http://source.android.com/source/building.html

Note: make sure the lunch output looks right! If the numbers don't match what you expect from http://source.android.com/source/build-numbers.html then you have synced to the wrong version and will need to repo sync -b [some other tag]

$ lunch full-eng

============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=2.3.3 <===== gingerbread
TARGET_PRODUCT=full
TARGET_BUILD_VARIANT=eng
TARGET_SIMULATOR=false
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
HOST_ARCH=x86
HOST_OS=darwin
HOST_BUILD_TYPE=release
BUILD_ID=GRI40 <===== matches gingerbread on http://source.android.com/source/build-numbers.html
============================================

Make It

$ make -j8 # 4-cpu, 8-core machine


Don't Wait: Watch for classes.jar


The build takes about 20-30+ minutes, but you don't have to wait for the full build. Watch for out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar. When it appears you can grab it and move it somewhere handy:

$ mkdir -p ~/android-real-jars/gingerbread-233
$ cp /Volumes/android/gingerbread-233/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/*.jar. ~/android-real-jars/gingerbread-233/

Also: download kxml2-2.3.0.jar and put it in the same directory you moved classes.jar. http://www.findjar.com/jar/net/sf/kxml/kxml2/2.3.0/kxml2-2.3.0.jar.html


Using the Jars with Robolectric

From Maven 2.0-SNAPSHOT



From Robolectric Source

$ git clone git://github.com/pivotal/robolectric.git
$ cd robolectric
$ git checkout origin/reviscerated # this is the "use real jars" branch

  • Open RobolectricTestRunner.USE_REAL_ANDROID_SOURCES and make sure it is set to true.
  • Look at RobolectricTestRunner.getDefaultLoader() and make sure the paths point to the real jars you copied above. You can also use environment variables for these. See Setting ANDROID_HOME and ANDROID_REAL_JARS_HOME below.
  • Rebuild the jars in real-jar mode:
    $ ant clean jar-all
  • Replace your robojectric.jar with target/robolectric.jar or target/robolectric-all.jar

If you are using IntelliJ you might need to fix the Project Structure => Modules => Dependencies.

Setting ANDROID_HOME and ANDROID_REAL_JARS_HOME

You need to tell Robolectric some extra information when in real-jars mode. At this time you can either hand-edit RobolectricTestRunner and change the defaults or set environment variables:

# in .bash_profile or something

export ANDROID_HOME=/Users/pivotal/android-sdk-mac_x86
export ANDROID_REAL_JARS_HOME=/Users/pivotal/android-real-jars/gingerbread-233


Intellij most likely won't see those variables. Set them in the run configruation or default run configuration for JUnit:





Issues

  • Real jars mode seems to be incompatible with Roboguice at the moment. Sometimes it works, somtimes not.
  • Running ant clean test results in MethodGenerator test failures -- javassist.NotFoundException for Views. mvn clean test succeeds.
  • We would like to find the minimum time and effort to generate real jars.
  • There are likely errors in this blog post. We will be updating it as issues are found.

Thursday, February 3, 2011

How to work on Robolectric in parallel with another project

The Robolectric framework is designed to allow us to create and install test doubles for elements of the Android API as we encounter the need for them during our test and development activities. This should come as no surprise since Robolectric was born of an Agile development environment that strives to create software artifacts on the basis of demonstrated need rather than speculation. A consequence of this approach is that the most effective way to work with Robolectric is to also work on Robolectric. When your project encounters a part of the Android API that is not yet covered by Robolectric, or which is not covered in a way that you like, it is easy to add the needed functionality to Robolectric and move forward. This is similar to writing a new mock object in order to facilitate testing except that the new functionality is added to the permanent test infrastructure rather than being inlined into the test code.

The Robolectric documentation contains instructions about how to write and modify the Shadow Classes that serve as the test doubles for the Android code, but little is said about how to integrate active Robolectric development into your working environment. This article addresses how to obtain and build the Robolectric source so that your changes are immediately visible to your tests; how to track upstream changes to Robolectric; and how to create pull-requests for code that you want to contribute to the Robolectric project. These instructions are completely compatible with the instructions for registering your own private Shadow Classes contained in the Robolectric documentation about customizing the RobolectricTestRunner. So, you will still be able to write Shadow Classes (including subclasses of existing ShadowClasses) that are specific to your own application and register them in your custom TestRunner at the same time as you are writing new classes, or other extensions to Robolectric functionality, that are meant for consumption by the wider Robolectric community.

About git
The source code for Robolectric lives in a public git repository on the GitHub website. Unfamiliarity with git should not be an obstacle to using a live version of the Robolectric source in your project. This article documents all of the commands that are needed and GitHub has excellent help for both git and GitHub including how to install git on Windows. Git does have a couple of idiosyncrasies, almost always for good reasons, and we will discuss them as we run into them.  The first of these is that it is a distributed revision control system which means that there are multiple copies of the repository. In our case there will be three repositories in addition to your working copy of the code. The first repository lives on GitHub and is the primary repository for the Robolectric code. The second repository will also live on GitHub and will be the primary repository for your organization's copy of the Robolectric source code and your gateway for sharing contributions that you wish to share with the community. The third repository lives on your personal development machine and is the one that sometimes catches people by surprise. This article will treat it as just a gateway between your machine and GitHub, but it is a fully fledged revision control system for the work done on a single instance of a project on a single machine. With that information in mind we can proceed.

Forking Robolectric
The first thing you need to do is to create a place on GitHub for the changes you want to make. You don't have to use GitHub as your revision control solution for Robolectric if you don't want to, but you do need a way to move code between your organization and the main Robolectric repository and this is the mechanism for doing that.
  1. Login or create an account on GitHub. You may want to create a separate account for your specific organization or project.
  2. Navigate to the Robolectric project page at: https://github.com/pivotal/robolectric
  3. Click the button that says "Fork" (upper right corner, third line from the top, fourth button from the right)
    You now have your own private copy of the Robolectric source code in a repository in GitHub that knows how to track changes in the main Robolectric repository. Later we'll talk about how to merge changes from the main Robolectric repository into your own, and how to request that we pull some or all of the changes you've made into the main repository so that they can be used and maintained by the rest of the community.
  4. Click on the button with the clipboard icon next to the text that says: "This URL has Read+Write access"
    This will copy a URL to your clipboard that can be used to make a local clone of the repository
  5. On the command line, go to the location where you want the Robolectric project to be created (you do not have to create the subdirectory for it), type: "git clone " and then paste in the URL you just obtained. The command should look something like this:
    git clone git@github.com:your_user_name/robolectric.git
    When this command completes you will have a local copy of the Robolectric repository (in
    robolectric/.git) as well as a working copy of the code in the newly created robolectric directory.
  6. To build and test Robolectric type the following commands:
    For Maven builds:
    ant maven-install-jars
    mvn install

    This will locate your Android development kit and install android.jar into your local Maven repository and then build Robolectric, test it, and then install the resulting jars into Maven.
    For ant builds:
    ant clean test
    Which will build the Robolectric jar and run the tests.
Adding Robolectric as a Dependency in Your Project
Maven
If you are using Maven you should take note of the Robolectric version (currently 0.9.9-SNAPSHOT) from pom.xml file and add a dependency to your projects pom.xml that looks something like this:

<dependency>
    <groupId>com.pivotallabs</groupId>
    <artifactId>robolectric</artifactId>
    <version>0.9.9-SNAPSHOT</version>
    <scope>test</scope>
</dependency>

Ant
The easiest thing to do is to include the Robolectric sources as part of your compile.tests target something like this:


        

        
            
            
            
                
                
                
                
                
            
        
    

Where the key items are the addition of <src path="..robolectric/src/main/java"> as one of the source directories and <fileset dir="../robolectric/lib/main" includes="*.jar"> in the classpath.

Eclipse and IntelliJ
As mentioned elsewhere both of these IDEs can import Robolectric using its pom.xml file. In Eclipse this is done using the M2Eclipse plugin. In the "Configure Build Path" dialog for your test project, under the Projects tab, just add Robolectric and any changes you make to the Robolectric project will be picked up. Similarly in IntelliJ, Robolectric can be added as a module by navigating to the "Project Structure" dialog, selecting "Modules" under "Project Settings" and then clicking the plus icon and navigating to the pom.xml file.

Pulling Upstream Changes
From time to time you will want to pull the changes that have been made to the main Robolectric repository into your own workspace. The following command will connect the two together in order to make that possible:


git remote add pivotal git@github.com:pivotal/robolectric.git


This makes pivotal an alias for the main repository. Now you can pull down the latest Robolectric sources and merge them with your local changes with:


git pull pivotal master


If you have made substantial changes to the Robolectric code you may need to resolve merge conflicts before proceeding further. Ensure that all of the tests run with this command:

mvn clean test

Submitting Pull Requests
Once your changes to Robolectric are complete and the tests for them are all passing, you will often want them to become a permanent part of the Robolectric codebase so that they can be used and maintained by the rest of the community. You can accomplish this by pushing your changes to your repository on GitHub and submitting a pull request to the Robolectric team.

The first step in this process is to get the code in your repository to look the way you want the code in the Robolectric repository to look. Start by pulling in any upstream changes, as described above, and ensuring that the entire test suite passes. Once everything is working the next step will be to commit your changes to your local repository in preparation for pushing them to GitHub. This is accomplished with the following command:

git commit -a -m"some commit message"


In this command the -a means "add all of the changes I've made to the list of changes to be committed" and the -m allows you to put your commit message on the command line. If it is omitted git will open an editor and ask you to enter your commit message there.


Ensure that all of your changes have made it into your repository:


git status


The resulting message should tell you which branch you are on and that there is nothing to commit.


Once your changes are in your local repository you can push them to the GitHub repository with the following command:

git push origin master


This command may fail if you have not set up an SSH key for your GitHub account. If that happens, these instructions will help you out.


Running git push makes it possible for the Robolectric team to get access to the changes that you want to add. You can request that we add these changes to the project by submitting a pull request from the GitHub website. Log in to GitHub and select the forked Robolectric repository from your list of repositories on the right-hand side of the page, and then click on the "Pull Request" button from the button bar in the upper right corner. Fill in the form to let us know what to expect from your pull-request and then click the "Send Pull Request" button. We like to use pull request titles as story names in Tracker so please make yours suitably descriptive.

We are responsive as possible to pull requests, often putting them ahead of our current priorities, in order to encourage the Robolectric community to remain active. Many factors, including the presence of passing tests, commitments of our day jobs, compatibility with our roadmap, and technical and legal considerations, can affect our ability to quickly address each pull request. Regardless of our ability to respond, we want everyone who makes a contribution to Robolectric to know that we, and the rest of the community, appreciate their efforts very much, and give our heartfelt thanks for all of the great contributions we have already received.