lundi 12 mars 2018

UI instrumentation tests with proguard enabled

Let's say your app has a class Foo, and you are using this class on your app and your instrumentation code.

But this class becomes obfuscated in the app APK, so your instrumentation code loses the reference to it.

Is it possible to continue obfuscating the class at the same time you are using the instrumentation code?

To illustrate consider the following case.

Your project has the following build file:

apply plugin: 'com.android.application'
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "ademar.testproguard"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        resConfigs "en"
    }
    buildTypes {
        all {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            testProguardFile "proguard-test-rules.pro"
        }
    }
}
dependencies {
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
}

The proguard-test-rules:

-dontobfuscate
-dontwarn
-dontshrink
-dontoptimize
-dontusemixedcaseclassnames
-ignorewarnings
-keepattributes *Annotation*
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    package="ademar.testproguard"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:name=".App"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

The App class:

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        new Foo().foo();
    }

}

A simple class that will be obfuscated:

public class Foo {

    public void foo() {
        Log.d("TestProguard", "Success called");
    }

}

The UI test:

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {

    @Test
    public void testA() throws Exception {
        Context appContext = InstrumentationRegistry.getTargetContext();
        assertEquals("ademar.testproguard", appContext.getPackageName());
    }

    @Test
    public void testB() throws Exception {
        new Foo().foo();
    }

}

Now if you run the tests, the testA will pass, notice the App was created, the obfuscated Foo class was called, and you log Success called was successfully called. In another hand the testB will receive the following exception:

java.lang.NoClassDefFoundError: Failed resolution of: Lademar/testproguard/Foo;
   at ademar.testproguard.ExampleInstrumentedTest.testB(ExampleInstrumentedTest.java:23)
   at java.lang.reflect.Method.invoke(Native Method)
   at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
   at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
   at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
   at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
   at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
   at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
   at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
   at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
   at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
   at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
   at org.junit.runners.Suite.runChild(Suite.java:128)
   at org.junit.runners.Suite.runChild(Suite.java:27)
   at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
   at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
   at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
   at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
   at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
   at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
   at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
   at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58)
   at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:375)
   at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2075)
Caused by: java.lang.ClassNotFoundException: Didn't find class "ademar.testproguard.Foo" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock.jar", zip file "/data/app/ademar.testproguard.test-yIIGwW8RoqUKvzfKeMd__Q==/base.apk", zip file "/data/app/ademar.testproguard-L6M8bDawepTV9VltDF2TqA==/base.apk"],nativeLibraryDirectories=[/data/app/ademar.testproguard.test-yIIGwW8RoqUKvzfKeMd__Q==/lib/x86, /data/app/ademar.testproguard-L6M8bDawepTV9VltDF2TqA==/lib/x86, /system/lib, /vendor/lib]]
   at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:125)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:312)

As you can see the app APK has the Foo class obfuscated

Main apk

The Instrumentation class also has the Foo, without obfuscate it:

Instrumentation APK

Aucun commentaire:

Enregistrer un commentaire