Continuous Integration with Android and Bamboo

In a previous post, I spoke about how to use Robolectric to unit test Android applications without having to resort to the emulator. In this post, I’ll detail setting up Atlassian Bamboo to run continuous integration of an Android application.

What is Bamboo?

For those of you who don’t know, Atlassian Bamboo is a Continuous Integration system. Many people have probably heard of Confluence (think internal wiki and collaboration software), Stash (think BitBucket) or JIRA (issue tracking software). These three systems are widely used in Enterprise software development shops and many businesses in general.

To me, Bamboo is a vital key in the software development process. Combining Bamboo with JIRA and Stash provides some really great features, such as creating task branches directly from your issue tracking software, seeing what issues were resolved with a commit, and being able to track released versions with all of their fixes detailed in a nice changelog.

The Problem

When performing Android development, I needed to get Bamboo building my projects. Doing so involved two separate pieces:

  1. Installing the Android SDK so that Bamboo had access to it.
  2. Configuring Bamboo to build the actual project.

Note that I’m assuming that you already have a JDK installed on the server, and that you’ve already installed Bamboo.

Installing the Android SDK

This task was fairly straight-forward. First, I had to download the actual Android SDK from Google’s website. This meant visiting the Android SDK website, and then clicking on View all downloads and sizes. I just wanted the SDK tools, not the ADT bundle, so I selected the android-sdk_r23.0.2-linux.tgz package which was about 140 MB in size (note that when you read this, the SDK may be several versions more advanced – your mileage, therefore, may vary).

Once downloaded, I copied it up to my server, and expanded it in a place where all users of the server could access it:

sudo mkdir /opt/android
cd /opt/android
sudo tar xvzf ~thomas/android-sdk_r23.0.2-linux.tgz

This expanded the SDK to the following directory:

/opt/android/android-sdk-linux

I then fired up the SDK manager to install the necessary components. First though, I had to log out, and then SSH with X-11 forwarding turned on so that I could see the GUI it provides (note that you can do everything without the GUI using the --no-ui option for the android update sdk command):

ssh -X 10.0.0.60

Next, I actually ran the android tool as root:

sudo /opt/android/android-sdk-linux/tools/android

However, this resulted in an error message:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no swt-pi-gtk-3550 or swt-pi-gtk in swt.library.path, java.library.path or the jar file
    at org.eclipse.swt.internal.Library.loadLibrary(Unknown Source)
    at org.eclipse.swt.internal.Library.loadLibrary(Unknown Source)
    at org.eclipse.swt.internal.gtk.OS.(Unknown Source)
    at org.eclipse.swt.internal.Converter.wcsToMbcs(Unknown Source)
    at org.eclipse.swt.internal.Converter.wcsToMbcs(Unknown Source)
    at org.eclipse.swt.widgets.Display.(Unknown Source)
    at com.android.sdkmanager.Main.showSdkManagerWindow(Main.java:402)
    at com.android.sdkmanager.Main.doAction(Main.java:390)
    at com.android.sdkmanager.Main.run(Main.java:150)
    at com.android.sdkmanager.Main.main(Main.java:116)

The quickest way to solve this problem (as discussed here), was to install the libswt-gtk-3-java package:

sudo apt-get update
sudo apt-get install sudo apt-get install libswt-gtk-3-java

I then re-ran the SDK manager:

sudo /opt/android/android-sdk-linux/tools/android

And got the normal SDK manager window up. I then proceeded to install the components I needed as usual:

android-sdk-remote

Configuring Bamboo

The next step was to configure Bamboo to actually perform the build. I went ahead and created a new Plan for my code repository. When configuring the tasks, I left the default Stage in place. Under the default stage, I left the default Source Code Checkout task in place, but added a new task to perform the build. Because I was using Gradle, I created a Script task:

bamboo-script-task

The script task required only two pieces of information. For the actual script, I used the Gradle wrapper that I had checked into source code control, and used the build target (./gradlew build). In addition to that, I had to tell the script where the Android SDK was located. For that, I created an environment variable called ANDROID_HOME and set it to where the SDK was located. Here is a screenshot of the configured task:

bamboo-gradle-build

When I first ran the build, I was surprised that no test results were being reported, even though the build target in the Gradle wrapper runs the actual unit tests. I realized I needed to use a JUnit Parser task to parse the test results. All that was needed for that was to specify the directory where the results of the unit tests would be located. I did that with a simple ANT-style path:

**/test-results/*.xml

Here is a screenshot of the configured JUnit Parser task:

junit-parser-test-results

With that complete, I triggered a manual build, and checked the test results tab to make sure everything ran:

bamboo-test-results

83 tests run, all successful. Excellent!

Wrapping Up

As you can see, it is fairly straightforward to get simple unit testing going on Bamboo with the Android SDK. This assumes of course, that you don’t actually need the emulator, and are using a package such as Robolectric. In a future post, I’ll talk about some more of the challenges of developing a CI strategy with the Android SDK, and how to solve them.

Android Unit Testing with Robolectric

I have been doing a lot of Android programming of late. One of the tasks that I originally had was to get a continuous integration environment going so that I could quickly and efficiently test and integrate my new code with my existing codebase. In this post, I’ll talk about how I set up Robolectric using Gradle.

Why Robolectric?

If you’ve done any kind of Android development, you probably already know part of the answer to that question. As it turns out, testing on Android can be a little bit of a pain in the rear. While Google provides a really nice SDK, all of the functions in the android.jar file are stubbed out. This means when you try to unit test anything that makes a call against it, you get the wonderful Stub! error.

To be fair, Google has provided a bunch of utilities and classes to help make unit testing possible. But, at the end of the day, you usually have to resort to pushing your test code onto an emulator (slow for a TDD approach), or an actual device (painful if you have a CI environment that doesn’t have access to one). You can try to get around this by Mocking the Android objects you need, but that turns into a lot of work.

Robolectric helps by providing a mock for you, allowing you to run your tests on a normal JVM instead of having to push them to the emulator or an actual device. Plus, it plays nicely with the Gradle lifecycle, allowing you to do things like ./gradlew clean test when you want to run your unit tests. Plus you can use JUnit 4 to test your vanilla Java classes.

Setting it Up

When I was trying to set up my environment, I ran into quite a number of articles that tried to explain how to configure your tests, and how to add the required statements to your build.gradle file. A lot of them were outdated – the Android development platform evolves rapidly. Keep that in mind when you read this – these instructions may no longer apply if it’s been even a month or two since this post was written! In the end, the instructions for the robolectric-gradle-plugin on their GitHub repository got me most of the way there, with a few tweaks to deal with certain problems (more below).

The first thing that I did was add the required statements to my build.gradle file. First, I added the required buildscript parameters:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.robolectric:robolectric-gradle-plugin:0.13.1'
    }
}

This just tells Gradle to use Maven Central repository when trying to get dependencies for the build script itself (project dependencies are declared in another block). I then applied the plugin:

apply plugin: 'robolectric'

Finally, I added the required androidTestCompile dependencies:

dependencies {
    repositories {
        mavenCentral()
    }
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile 'org.robolectric:robolectric:2.3'
    androidTestCompile 'junit:junit:4.11'
}

Notice I also added the jUnit dependencies – this is because I have some vanilla classes I want to test as well using JUnit 4. Essentially, this just tells Gradle that the androidTest lifecycle target requires Robolectric version 2.3, as well as JUnit 4.11.

This is all that is really required to get Robolectric up and running. I ran a sync in Android Studio…  and got a nice big error:

Error:(11, 0) Cause: org/robolectric/gradle/RobolectricPlugin : Unsupported major.minor version 52.0

A New Problem

The Unsupported major.minor error was obvious: 52 is the version number for Java 8. I was using a Java 7 SDK, which has a major.minor version of 51. Looks like the Robolectric Gradle plugin on Maven Central was built with Java 8.

While I could update my Java SDK to Java 8, I was worried that it wouldn’t play nice with my existing codebase. Instead, I figured it would probably be easier to download and compile the Robolectric Gradle plugin using my version of Java, and then include it as a static import in my code. This has some benefits – if I check the Robolectric Gradle plugin into source code control, then I’m always guaranteed of having a good version number without having to rely on automated imports. So, away I went.

First, I cloned the Git repo:

git clone https://github.com/robolectric/robolectric-gradle-plugin.git

Second, I built the package. This was pretty simple since it uses a Gradle wrapper:

cd robolectric-gradle-plugin
./gradlew build

This generated the plugin’s jar files (including sources and javadocs) and put them in the following directory:

build/libs

Third, I copied all the jars into my application’s libs directory. Here is another little tip – for Gradle to pick up the jars, they needs fall under a certain directory structure. In this case, the compiled jar files needed to be copied to:

libs/org/robolectric/robolectric-gradle-plugin/0.13.1/

Fourth, and finally, I modified my buildscript portion of build.gradle to use a local Maven import from the local libs directory, instead of using Maven Central:

buildscript {
    repositories {
        maven {
            url "./libs"
        }
    }
    dependencies {
        classpath 'org.robolectric:robolectric-gradle-plugin:0.13.1'
    }
}

I synced the settings file once again in Android Studio and it worked without errors.

Creating Unit Tests

Next, I tried creating unit tests (I actually already had a bunch of them, so I was already good to go). According to the README on the GitHub repository for the Robolectric Gradle plugin, I put my tests under the src/androidTest/java directory. To test it out, I created a unit test that I knew would fail, called FailTest.java:

package ca.craigthomas.sampleproject.models;
 
import org.junit.Test;
 
import static org.junit.Assert.assertTrue;
 
public class FailTest {
    @Test
    public void thisTestShouldFail() {
        assertTrue(false);
    }
}

I then got Gradle to run the unit tests (fingers crossed that it would work):

./gradlew test

And got the following output:

ca.craigthomas.sampleproject.models.FailTest > thisTestShouldFail FAILED
    java.lang.AssertionError at FailTest.java:10
 
82 tests completed, 1 failed                                  
:app:testDebug FAILED

Success! As you can see, it also ran the other unit tests that I already had. If you want to see a nice report of your unit tests (say if you don’t have a CI environment that does this for you automagically), an HTML report will be generated at:

build/test-report/index.html

Wrapping Up

In this blog post, I discussed the Robolectric Gradle plugin, and demonstrated how to integrate it into a simple Android project. In a future post, I’ll describe how to set up a CI environment to use the Gradle wrapper and the Android SDK to run unit tests automatically.