Coming Up for Air

Testing Android Applications with Maven, Android-x86 and VirtualBox

cubtracker logo 220 white

For a few months now, I’ve been working on a small application called Cub Tracker which is designed to help Cub Scout den and pack leaders track the progress of the scouts assigned them. I’m a big fan of testing, so I’ve done my best to follow TDD as I’ve worked on the app. Early on, it became clear that I needed a better way to test, as the official Android app is slow and unreliable at times. Via Twitter, I was turned on to Android-x86 and after a couple nights of hacking, I think I have a usable installation of Android-x86 under VirtualBox that has sped up my testing considerably. In this article, I’ll show you how I did it.

First up, let me clarify something, namely, my claim that the Android emulator is unreliable. Unless I’m just really unlucky or doing something really wrong, sometimes the emulator comes up, for lack of a better term, in a funky state. I can unlock the screen, interact with the "device" etc., but adb doesn’t see the emulator, so I can’t deploy my app (e.g., adb logcat sits waiting for the device to come online forever). Usually, killing the zombie emulator and starting a new one solves the problem, but that type of manual interaction doesn’t work under CI. With what little experience I’ve had with Android-x86 and VirtualBox, that doesn’t seem to be a problem anymore. The device starts up quickly and is almost immediately ready to use.

So how does one set up a VM for this? While there are probably a myriad of ways, I’ll give you two. You can follow these instructions, or you can download this VirtualBox appliance I exported. The appliance has the lock screen disabled, and the ethernet configuration set to a static IP, so, assuming the IP works on your network, you can just import the appliance and start using it.

For those that want to build your own VM, once you’ve finished the instructions linked above, I would suggest setting a static IP (Menu → Settings → Ethernet Configuration) and disabling the lock screen (Menu → Settings → Location & security → Set up screen lock).

*A quick note on navigating in the emulator*: To go back, you press ESC. To emulate menu keypresses, etc, click on the clock in the status bar. After a moment or two, it will tell you that "you can touch the menu bar to do the function of menu now". Once that’s displayed, click on the status bar will go Home, and click and swiping right will emulate a Menu press.

So now, one way or another, you have your VM setup, but how do you integrate that with your tests? While Maven’s XML doesn’t make it easy, judicious as of antrun saves the day. Here’s a sample POM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <parent>
        <groupId>com.example.application</groupId>
        <artifactId>application-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>test-suite</artifactId>
    <packaging>apk</packaging>
    <name>My Test Suite</name>
    <dependencies>
        <dependency>
            <groupId>com.google.android</groupId>
            <artifactId>android</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.android</groupId>
            <artifactId>android-test</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>application</artifactId>
            <version>${project.version}</version>
            <type>apk</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>application</artifactId>
            <version>${project.version}</version>
            <scope>provided</scope>
            <type>jar</type>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <version>1.7</version>
                <executions>
                    <execution>
                        <id>startvb</id>
                        <phase>prepare-package</phase>
                        <configuration>
                            <target>
                                <echo>***** Starting VirtualBox</echo>
                                <exec executable="adb">
                                    <arg value="kill-server"/>
                                </exec>
                                <exec executable="VBoxManage">
                                    <arg value="startvm"/>
                                    <arg value="Android"/>
                                </exec>
                                <waitfor maxwait="3" maxwaitunit="minute">
                                    <and>
                                        <socket server="192.168.1.200" port="5555"/>
                                    </and>
                                </waitfor>
                                <exec executable="adb">
                                    <arg value="connect"/>
                                    <arg value="192.168.1.200"/>
                                </exec>
                            </target>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stopvb</id>
                        <phase>verify</phase>
                        <configuration>
                            <target>
                                <echo>***** Stopping VirtualBox</echo>
                                <exec executable="VBoxManage">
                                    <arg value="controlvm"/>
                                    <arg value="Android"/>
                                    <arg value="poweroff"/>
                                </exec>
                            </target>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                <artifactId>android-maven-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <sdk>
                        <platform>8</platform>
                    </sdk>
                    <undeployBeforeDeploy>true</undeployBeforeDeploy>
                    <enableIntegrationTest>true</enableIntegrationTest>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Scroll down to about line 40 or so. Here, there’s an antrun execution block that starts the VirtualBox VM. In my case, it’s called "Android", so you’ll want to change that as appropriate. Ideally, that would be a property so that you can target different VMs for different Android versions. I’ll leave that as an exercise for the reader. : ) First up, we kill the adb server. This might be overkill (no pun intended. Honest! : ), but I’ve had issues where adb was certain it was already connected to the device in situations where I run the tests over and over. Next, we start the VM, and then we wait for it to listen on port 5555. The startvm command finishes pretty quickly, but that doesn’t mean we’re ready to deploy our test app yet, so wait. Finally, we tell adb to connect to our VM. From there, it’s the standard Maven Android Plugin.After our tests are done, we tell VirtualBox to shut down our VM.

Sharp-eyed readers have probably noted a dependency listed twice, that of the application to test. That oddity is, as best as I can tell, to allow the test application to compile (via the Jar dependency) and to tell the Maven plugin what application archive to deploy (via the apk dependency) so that the tests have something to run against.

The setup isn’t perfect, sadly. For example, if a test fails, the VM isn’t torn down (it’s likely just a poor choice of Maven phase), and, ideally, those hard-coded values would be properties. It does, however, seem to work fairly well for manual runs, so while there may still be lingering issues preventing unattended runs under, say, Hudson, this feels like a good step in the right direction. The best part is that this can easily be imported into NetBeans or IDEA thanks to their great Maven support (read as: Eclipse is no longer required. : )

Give that a whirl and let me know what you think. If you find a way to improve it, I’d love to hear about!

Quotes

Sample quote

Quote source