I've needed to recently introduce ProGuard on Android because of issues with Scala on Android. I need ProGuard for its shrinking feature, which removes classes presumed to be unused. I'm very concerned about the impact of removing classes on testability.
As it stands, I write unit tests that run on the host and acceptance tests that run the fully integrated application on the Android platform.
Normally, I would be comfortable with relatively complete unit test coverage and spotty acceptance test coverage. However, given that in my code I use Guice dependency injection heavily, so far it's been my experience that ProGuard removes code in a manner that's difficult for me to predict. Because of this it's very likely to cause me to introduce bugs.
This leads me to believe that I need to write acceptance/platform tests that achieve full coverage because at any point, there may be a missing class.
Do others have this experience? If so, what has been your testing strategy? Or with experience, do you become more confident that the classes that ProGuard is removing are truly unneeded?
ProGuard will not break your application until it attempts to use reflection or Class#forName on removed classes and/or obfuscated members.
From my experience (with obfuscated Scala on Android too) it is really easy to spot problems caused by ProGuard to your Android application using the simple smoke tests. You know what libraries you include in your project. If some of them uses reflection or Class#forName - perform smoke test on them. Then exclude the necessary classes/members from the ProGuard configuration.
Remember also that you can automatize testing of your obfuscated project using the ActivityInstrumentationTestCase2 and emulator. If you plan to use ProGuard on your project, always perform instrumentation testing on obfuscated APK.
In conclusion - fear not. ProGuard-related problems are relativity easy to spot.
We've been both unit testing and "fully" testing our ProGuard-ed application for quite a while now, and we've had no "real" problems. The only issues we run into is when we use some library methods in our tests that aren't used in the main application; in these cases ProGuard will remove the code from the libraries and we would have to manually add the specific methods to proguard.cfg.
Oh, and we also use Guice :)
Related
Colleagues,
I am having hard time to find a library/framework/etc... which could help me to generate unit tests in Android.
I know that in Java there are multiple solutions, which could generate unit tests based on the source code. Are you using anything like this for Android?
(My project if fully written in Java).
Use just JUNIT - this should help :
https://developer.android.com/training/testing/unit-testing/local-unit-tests
Use Unit Test Architect
I am in the same boat as you were/are. Although TDD should be the approach for writing tests, but there may be a lot of untested code already written many times in larger projects.
One day I got frustrated with writing test cases of existing, older codebase. Hence, I thought of auto generating all the unit test cases.
I have created an Open-source Gradle Plugin which can be used for the above task. It is already hosted on mavenCentral. I have used it to generate test cases for my projects. But it can be used in any gradle project, (android, java, kotlin, kotlin+java). It may be rough around the edges but it has done it's job well for me.
BuildScript Dependency:
classpath "io.github.orange-3:unit-test-architect:$PLUGIN_VERSION"
I have an error after switching from Dagger 2.5 to 2.12 when using proguard on my release build.
DaggerGraph.java:662: error: cannot find symbol
ReportingService_MembersInjector.injectA(instance, provideDataLayerProvider.get());
I have an Android Library that is compiled and obfuscated and an Android App that includes that Library.
The graph is generated using components from both modules.
Any hints?
Thanks
PS. With Dagger 2.5 it is working without problems.
PPS. The debug build without proguard is also working good with Dagger 2.12
Speculative answer: This may have less to do with Proguard and more to do with some of the optimizations made specifically in Dagger 2.12.
Because you're running Dagger on the library you're created, and then consuming that library from a different Dagger app, Dagger gets two chances to run: First for the library, which creates your ReportingService_MembersInjector, and then a second which presumably consumes that same ReportingService. Between those steps, Proguard can effectively do whatever it wants to classes that you haven't marked with -keep and related switches. My hunch is that Dagger needed to keep your injectA method as of 2.5, but the 2.12 optimizations no longer need you to keep that method, so Proguard eliminates it.
In your Android app that consumes the library, Dagger detects a class named ReportingService_MembersInjector, so it doesn't create another copy, and incorrectly assuming it contains all of the methods that it would generate.
I think the root of the problem is that your library exposes an #Inject-annotated class that your outer (app) Dagger graph is evidently consuming directly, and then you are also keeping the Factory and MembersInjector classes that Dagger provides adjacent to it. Even if you were to properly -keep your generated MembersInjector, Provider, and Factory classes, you might experience version differences between the inner obfuscated library and the outer copy of Dagger that would make for different sorts of trouble. Instead, provide a factory or other official way of creating your library class from the outside of the library, so there's no reason that the two Dagger runs can interfere with one another.
I have written many unit and instrumented tests for my Android app. So far, I only run these against the debug build variant. Is it necessary to run tests against the release build variant? What difference can there be that might give different results from testing? The main one that I can think of is when ProGuard is enabled, which I haven't done. What will ProGuard do that makes it necessary to run my test suite? What other issues should I be aware of that require testing the release build variant?
Is it necessary to run tests against the release build variant?
I think you should.
What difference can there be that might give different results from testing?
A couple of examples:
You might have code that uses fields of the BuildConfig class to enable/disable certain workflows. Some libraries might also use that, especially BuildConfig.BUILD_TYPE. It's common to do things like:
if (BuildConfig.BUILD_TYPE.equals("debug") {
ACRA.init(...);
Stetho.init(...);
...
}
but have code fail in release builds due to trying to use components/libraries that were not initialized correctly.
As you mentioned, ProGuard might throw away some of your classes unless it's properly configured (e.g. imagine you forgot to add rules for some 3rd party library). Running your tests against the release variant ensures that the ProGuard configuration is correct.
What will ProGuard do that makes it necessary to run my test suite?
ProGuard might remove classes/methods/fields that are, for example, loaded via reflection unless you add the #Keep annotation to them. It might also rename classes used by libraries like Realm, Retrofit, Gson or Volley resulting in all unit and integration tests passing on debug builds (where ProGuard isn't enabled) but failing on release builds. You definitely want to test these before shipping our a new APK.
What other issues should I be aware of that require testing the release build variant?
The release build might also apply PNG crunching, specify different parameters via the buildConfigField method in Gradle, apply splits by ABI or density or enable/disable multidex and others. All of these can have implications in the way that your app works, so why not be on the safe side and test them too.
Another frequent problem that you can catch using these is ensure that you've not accidentally put code in the wrong location (e.g. /src/debug/java/) that happens to be loaded in debug builds but not in other variants.
For the moment I am developing small Android projects to practice with the Android prorgramming. However, once on the market, I would like to obfuscate / optimise the APK thanks to ProGuard. But this tool renames classes to obfuscate the code, so:
Is it safe to use tools like Android Query to write the code?
If it is not safe, what are some framework examples that can be used safely with Pro Guard?
What could be a solution to the problem? Or should I write everything using the good old Android style and forget about a "write less, do more" approach?
How do I identify the tools that are ProGuard-safe from the ones that are not?
I assume you want to use third party libraries (jar files). You could use a 3 step approach:
If the third party jar explicitly supports Android, it will have a proguard configuration. Usually this is a snippet that you merge into your proguard-project.txt.
If there is no such explicit support, you may still try to use the jar, obfuscate and test your app. If errors occur, gradually exclude classes from obfuscation until it works. A common problem is that libraries use reflection to instantiate classes and call methods which breaks after obfuscation.
Exclude the whole library from obfuscation. This will work in any case and proguard will not touch the library at all. (The Android toolchain will still repackage the contents of the jar into your apk which might cause problems.) This will also produce the least obfuscated result and should really be your last resort.
In any case, obfuscation is not a switch that you simply toggle. You'll need to get familiar with proguard config files which involves a learning curve.
I'm a long time Java developer with many years of Java EE, Ant, Maven, unit testing, mocks, etc. I'm currently using gradle to build android apps and am looking at unit testing them. And it's got me tearing my hair out!
My reading indicates that to test an app, I have to create another app inside it in a test directory. But I'm not sure how this embedded app can see the main apps classes. I presume that google came up with this because of something to do with the manifests which are different. I'm not sure why.
Without doing this embedded app system, I've been able to get unit tests to run by including the test classes with the main classes in the APK, adding the instrumentation declarations to the manifest, deploying it and running the test runners. But I don't want to be compiling test classes with app classes and including all the dependencies so that's not really an option and I'm not really sure of the effects of the changes to the manifest as I cannot find any documentation about the effects.
None of this is understood by gradle which follows the maven system of building. Also note that the android way seems to be that the embedded sub-project (test) is dependant on the main parent project, something that is totally contray to gradle and maven.
Another option seems to be separate the test project so that it's completely outside the app project. But really, I'd like to use a similar convention to maven and simply have tests in a separate directory, along with the manifest in test resources.
Has anyone managed to get testing on the emulators running unit tests following a more maven like directory structure?
You can try Robotium. It provides lots of features for a better testcase. You can have a look at it here.
Do you have to run the unit tests in the emulator? Isn't that too slow? I've been using robolectric ( http://pivotal.github.com/robolectric/ ) which creates shadow objects that work similar to mocks. I use progaurd ( http://proguard.sourceforge.net/ ) to strip out the tests for the release build.