Blog

How to run Android Instrumentation tests with ProGuard

04 Aug, 2013
Xebia Background Header Wave

The app I’m working on has an extensive, automated test suite built using Instrumentation and Robotium. These are white-box tests; they know about the Java classes that make up the tested app. This means that we were unable to run the tests after ProGuarding our application. I’ve never seen ProGuard break any working code after setting up the right exclusions. Nevertheless, it’s a bit questionable to have a huge automated test suite and not run it on the APK that we deliver to the Play Store.

ProGuard and Instrumentation Tests

One possible work-around is to use the UIAutomator framework, which does not have this limitation. There are several reason’s why we couldn’t: it requires API level 16 (Jelly Bean), our app supports API 8 (Froyo) as its minimum SDK version. Furthermore, we already had our test suite in place and rewriting it from scratch didn’t seem like a very good use of time. Here’s what we did instead.

The basic idea couldn’t be simpler. Can’t run the tests on an obfuscated package? Obfuscate the tests the same way! ProGuard has the -applymapping directive, which works in conjunction with the -printmapping directive to let you re-use an obfuscation mapping. If you’re using the standard Android Ant build scripts, you need to change a couple of things:

Update the ProGuard config of the main project

If you didn’t already add it for retrace, add a -printmapping directive to the ProGuard config of your main project.

Generates bin/proguard-mapping.txt
-printmapping proguard-mapping.txt

Update the ProGuard config of the test project

Your test project needs two things: the -applymapping directive to reuse the main project’s mapping file and as many -injars directives as needed to add all the plain versions of your program code and libraries.

[code language="bash"]
Reuse the main project's ProGuard mapping
-applymapping ../mainproject/bin/proguard-mapping.txt
# ProGuard is automatically fed the obfuscated code of the main
# project. It also needs the plain version:
-injars ../mainproject/bin/classes
-injars ../mainproject/libs/android-support-v4.jar
[/code]

You may have to add some -dontwarn directives. I got warned about a missing finish() method on my activites, which will never be a problem at run time ’cause it’s in the base class.

Make sure your tests are suitable

Some of our Robotium tests referred to activity classes by their simple name, e.g.:

[code language="java"]
private void goBackBackBackUntilHome() {
while ( !"MainActivity".equals(solo.getCurrentActivity().getClass().getSimpleName()) ) {
// sleep a bit and guard against infinite loop
solo.goBack();
}
}
[/code]

ProGuard will not recognize this as a class reference and will not transform it. Either of the following alternatives will work:

[code language="java"]
// Using a class literal
private void goBackBackBackUntilHome() {
while ( !MainActivity.class.isInstance(solo.getCurrentActivity()) ) {
// sleep a bit and guard against infinite loop
solo.goBack();
}
}
// Using a fully qualified class name
private void goBackBackBackUntilHome2() {
while ( !"com.myapp.MainActivity".equals(solo.getCurrentActivity().getClass().getName()) ) {
// sleep a bit and guard against infinite loop
solo.goBack();
}
}
[/code]

Furthermore, if you’re logging any activities’ class names to help you diagnose any test failures, you may now want to also log their titles.

Enable code signing

Both APK files have to be signed with the same key to allow the tests to run. You could use your actual release key, but I would advise against using that key so casually. Instead, configure your project to use the Android debug key. When it comes time to publish a release to the Play Store, simply strip the existing signature from the APK file and re-sign, re-align with your production key.

To sign with the debug key, add the following four lines to the ant.properties file in both the main project and the test project:

[code language="java"]
key.store=${user.home}/.android/debug.keystore
key.alias=androiddebugkey
key.store.password=android
key.alias.password=android
[/code]

Get ant to properly chain the projects

When you run any build target on the test project, it’ll automatically delegate to the tested project and build that too. Unfortunately, there’s an oversight in Google’s build file (sdk rev 22 / tools rev 18). When you run a release build of the test project, it goes ahead and does a debug build of the main project. You can easily work around this with a little ant property abuse:

user@machine test-project$ ant release -Dtested.project.target=release

This causes both projects to be built in release mode and the whole setup will now work.

Bonus tip: use -Dtested.project.target=help to skip building the main project if you’ve only changed your tests.

Run your tests

I’m actually not sure whether, after all this, the ant test command still works. We already had shell scripts in place that invoke adb install and adb shell am instrument directly instead of through Ant. You may need something similar.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts