How to switch/change testInstrumentationRunner dynamically with gradle - android

My project has 2 different groups of tests. One group runs only with the default AndroidJUnitRunner the other has to be run with a custom implementation TestRunner extends MonitoringInstrumentation.
Currently I switch the testInstrumentationRunner by editing the build.gradle each time I need to run the other group of tests:
android{
defaultConfig {
//testInstrumentationRunner "my.custom.TestRunner"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
I know that flavours can have their own testInstrumentationRunner but my current app already has 2 flavourDimensions. Using flavours is actually intended to have different versions of an app. I need 2 versions of the test application, both testing the same app with different testInstrumentationRunners.
I tried to change the testInstrumentationRunner by iterating over all test variants. There are actually multiple testInstrumentationRunner properties:
android.testVariants.all { TestVariant variant ->
//readonly
variant.variantData.variantConfiguration.instrumentationRunner
variant.variantData.variantConfiguration.defaultConfig.testInstrumentationRunner
}
But as soon as android.testVariants is called the build gets configured and all changes are not reflected in the build.
How can I change the testInstrumentationRunner (from a gradle plugin) dynamically?
I'd prefer to have 2 different gradle tasks, each using a different testInstrumentationRunner but testing the same variant. Because I intent to create a gradle plugin the solution should work as plugin too.

Have you considered using console parameter as a switch between two configurations? As simple as that:
android {
defaultConfig {
if (project.ext.has("customRunner")) {
testInstrumentationRunner "my.custom.TestRunner"
} else {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
}
And then for example run gradlew aDeb -PcustomRunner if you want to test using custom runner or gradlew aDeb to use default.
I know it's not rocket science but simpler is better, right? You can use it in your plugin too, just obtain the Project object and do the similar thing.

Since the android gradle plugin 1.3 it is possible to create separate test modules. Each of those test modules can have its own testInstrumentationRunner.
For a detailed example see the AndroidTestingBlueprint example project on github.
The solution from #johan-stuyts that got bounty works fine (or at least it did with the android gradle plugin 1.2). But it uses private APIs and creating a separate module is easier and future proof.

I had a similar issue, I used gradle's taskGraph. Based on your statement "My project has 2 different groups of tests." I'm going to assume you have different tasks defined, I'll call them testGroupOne and testGroupTwo:
task testGroupOne{
}
task testGroupTwo{
}
gradle.taskGraph.whenReady {taskGraph ->
if(taskGraph.hasTask(testGroupOne)){
testInstrumentationRunner "my.custom.TestRunner"
} else if (taskGraph.hasTask(testGroupTwo)){
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
This enables you to set the value of testInstrumentationRunner after configuration but before execution.

This is a partial solution, but maybe this will make things a bit less cumbersome for you: It does not allow you to run the tests with both test runners in the same build. If you want that you would have to clone all task instances of DeviceProviderInstrumentTestTask, which, in my opinion, is too complex and fragile.
This works for me (Gradle 2.4 and Android build tools 1.2.3). It uses internal APIs, so it is possible that this no longer works with the next release of the Android build tools.
You should modify the tasks generated by the Android plug-in after the project has been evaluated. The changes will then be used by the test tasks. The property that actually changes the used test runner is task.testVariantData.variantConfiguration.testedConfig.mergedFlavor.testInstrumentationRunner. Instead of making these changes during the configuration phase (i.e. outside a task), the changes are applied using a task, so your test runner is only used when requested. By forcing the test tasks to run after useMyTestRunner (if the latter is part of the build), the class name of the test runner will have been changed when a test task starts:
project.afterEvaluate {
task useMyTestRunner << {
tasks.withType(com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask.class) { task ->
task.testVariantData.variantConfiguration.testedConfig.mergedFlavor.testInstrumentationRunner = 'com.mycompany.MyTestRunner'
}
}
tasks.withType(com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask.class) { task ->
task.mustRunAfter useMyTestRunner
}
}
You can now run the tests using the default test runner configured for the flavor(s) with:
gradle :myApp:connectedAndroidTest
When you want to run the tests with your test runner use:
gradle :myApp:connectedAndroidTest :myApp:useMyTestRunner
I did not add checks for null for any of the properties retrieved using task.testVariantData.variantConfiguration.testedConfig.mergedFlavor.testInstrumentationRunner. You should add them for robustness. I think at least testedConfig needs attention. See getInstrumentationRunner() at https://android.googlesource.com/platform/tools/build/+/master/builder/src/main/java/com/android/builder/VariantConfiguration.java
You can use an import for com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask at the top of your build file, so you only have to use the simple name of the class.

Related

registerIdlingResources deprecation replacement doesn't work

I'm trying to replace my Espresso registerIdlingResources and unregisterIdlingResources deprecated method by using IdlingRegistry method according to Android documentation.
Some of my tests worked before the update and no longer work now. These tests work unitarily, but not together.
I noticed that there is a little difference with the old version (of Espresso class), this line is not present in IdlingRegistry class :
baseRegistry.sync(IdlingRegistry.getInstance().getResources(), IdlingRegistry.getInstance().getLoopers());
I think this sync method is very important for my custom IdlingResource...
How can I sync my looper without this line?
Edit: I use EspressoCore 3.0.1 with runner/rules 1.0.1
Edit2: Link of documentation who has been specify deprecation : Here and Here.
Be sure to run the latest version of androidx.test
If your tests run one at a time, but fail when run together, Android Test Orchestrator ("ATO") can solve that problem.
ATO runs each test method in a new process, so any in memory state is cleared.
From the docs, the basic gradle setup is:
android {
defaultConfig {
...
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// The following argument makes the Android Test Orchestrator run its
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
}
dependencies {
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestUtil 'androidx.test:orchestrator:1.3.0'
}
The docs also include setup for installing and using Android Test Orchestrator without gradle.
You can also use ATO on Firebase Test Lab:
via web UI
via CLI
If you still have problems with your IdlingResources you can try the busybee library which simplifies using IdlingResources and makes them easier to debug.
(disclaimer, I am a maintainer of that library)

'transformClassesWithMultidexlistForDebugAndroidTest' task fails when android.support.test.runner.AndroidJUnitRunner specified

I'm trying to run an espresso test on MultiDex app and am failing with the below error
Error:Execution failed for task > :transformClassesWithMultidexlistForDebugAndroidTest'.
java.io.IOException: The output jar is empty. Did you specify the proper > '-keep' options?
Here's the relevant section in my build.gradle
defaultConfig {
...
// Enabling multidex support.
multiDexEnabled true
dexOptions {
jumboMode true
}
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
I do have a workaround which is to:
comment out 'testInstrumentationRunner' line
Build the test
Uncomment the line
Run the test
This seems to work, but I need to re-do this any time I'm changing my test code, which is a major pain.
Could fine similar error online, but nothing specific to my case...
I am building using Android studio
My reputation doesn't allow me to comment, so will write as an answer:
Exactly the same workaround works for me:
Commenting the string with 'testInstumentationRunner'
Building tests. There will be an exception that Instrumentation Runner is not found
Uncomment the string
Run the test
UPD: This solution worked for me, though the question there is about different problem:
Android Espresso not working with Multidex gives "No tests found"

Gradle Skip Test

I am looking for a way to skip tests from one of the projects in a multi-build project. I don't want to use gradle build -x test because then it will skip test for all sub - projects.
Root
Sub P1
build.gradle
Sub P2
build.gradle
Sub P3
build.gradle
build.gradle
settings.gradle
I want to skip tests only for "Sub P3"
Can i configure my project(Sub P3) build file to skip tests?
Due to official user guide, there are 3 ways to skip some task in gradle.
The first 2, are: using predicate and exception throwing. Predicates are resolved during the configuration phase and may not pass to your requirements. StopExecutionExeptionthrowing could be added to the doFirst of every test and be throwed according to some condition. Bit that seems to be not very clear, because you have to modify both root script to set the condition and subroject sripts test tasks.
And the 3rd one is - disabling the tasks. Every task, has a enabled property (default is true), which is preventing task execution, if it was set to false. Only thing you have to do is to set this property for test task in your subproject. This can be done in sub projects buil script root, as:
test.enabled = false
This will work, if you didn't specify custom test tasks, if you did, you can disable all test by task type, as:
project(':subProject').tasks.withType(Test){
enabled = false
}
Previews 2 configurations must be included to the build script of the subproject, but since you have a root project, you can configure subprojecst from it's build script, via project() providing subproject's name:
project(':subProject').tasks.withType(Test){
enabled = false
}
For android there is no test task available in gradle
To skip unit tests in android you have to do this
android {
.
.
.
testOptions {
unitTests.all {
enabled false
}
}
}

gradle - Android Studio build too slow multidex application

When I add to my project the multidex:true, and make an Application class that extends from the MultiDexApplication, my project build time passed from 20 sec to around 90 sec.How to do some faster?
If you are like me who already tried Vic Vu's solution but still can't avoid enabling multiDex then you can try this (as long as your are using a device that has Android 5.0 and above).
Note This will only speed up your development build. Your production build will still be slow.
Basically you need to introduce 2 product flavors one for dev and one for prod.
Add multiDexEnabled true
android {
productFlavors {
// Define separate dev and prod product flavors.
dev {
// dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
// to pre-dex each module and produce an APK that can be tested on
// Android Lollipop without time consuming dex merging processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the application.
minSdkVersion 14
}
}
...
buildTypes {
release {
runProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
defaultConfig {
applicationId "com.something.something"
targetSdkVersion 23
versionCode 1
versionName "1.0.0"
multiDexEnabled true
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
And I have a class which extends Application so I had to override attachBaseContext()
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
If you are not extending Application simply use MultiDexApplication in your AndroidManifest.xml application tag.
Ensure that in your Android Studio Build Variants you are pointing to devDebug.
Read the complete instructions here https://developer.android.com/studio/build/multidex.html#dev-build
Supplying as an answer because this is better fit with the formatting.
To simply answer your question: No, there is no way. Multidex is a process meant to help lift the burden of the 65k method limit. This process is complicated and will simply make your build times longer.
The best you can can do is lower your method count.
In your build.gradle (supplied here) you're using:
`compile 'com.google.android.gms:play-services:8.3.0'`
But if you look at the most recent play services api you can pick and choose what services you actually need.
Look at Table 1 on this page.
Only use the ones you need. Google play services as a whole is somewhere around 30k methods.
That should help.
Multidexing uses more memory. As you get closer to your max heap size in Java you'll find Java spends more time doing GC than it does doing any real work, this can slow things down a lot.
I'd strongly recommend increasing the max heap size when using multidex. Add the following to the android closure in your build.gradle file to make the max heap size 4GB (Make it larger/smaller if you wish):
dexOptions {
javaMaxHeapSize "4g"
}
It depends.
You haven't specified it in your question, but if you just want to speed-up your development builds - then you can avoid the extra work. Official documentation includes a whole section about that.

How to sync Gradle/Android versionCode between build server and workstation

Currently we have Bamboo listening to a Git repository on any changes. When a change occur the build process starts and increases the (Bamboo) build number by one.
I thought it would be nice to use this same build number for the Android project (versionCode) so that the user of the app can always refer to the actual build he/she received. This way the build number goes from 1 to 2 on the build server. Only the workstation doesn't know about this and still uses version 1.
Is there any way to sync this build number?
Tried:
One possible solution a friend of mine suggested was to use a git command to get the commit number: git rev-list HEAD --count which is awesome. The only downside of this is that you cannot properly change build number within Bamboo. So the build number of Bamboo should be leading.
Btw, I'm using Android Studio with Gradle
Bamboo can be configured to set the versionCode value in the manifest to the build number when building, so propagating this change back to where the development is happening should be unnecessary. To configure Bamboo to do this, add a Script task to the build plan (before the actual build task) with a body of:
sed -i 's/android:versionCode="[[:digit:]]*"/android:versionCode="${bamboo.buildNumber}"/' AndroidManifest.xml
Yes, it is quite easy with gradle. You have to retrieve the latest build result in the gradle script and put it in the versionCode.
Here it is what you need (remember to change the variables to refer your server):
def getBambooNumber(){
def url = "https://bambooServer:bambooPort/rest/api/latest/result/PROJECT-BAMBOO-ID/latest.json?os_authType=basic".toURL()
def authValue = "USER:PASSWORD".bytes.encodeBase64().toString()
def json = new JsonSlurper().parseText(url.getText(requestProperties : ["Authorization" : "Basic " + authValue]))
return json.buildNumber + 1 // +1 to Get the new build number
}
def bambooBuild = bambooBuildNumber()
android {
...
defaultConfig {
...
versionCode bambooBuild
//This allows you to access in BuildConfig to the bamboo build
buildConfigField "Integer", "BAMBOO_BUILD", "${bambooBuild}"
...
}
...
}
Let me know if this works for you.

Categories

Resources