问题
Problem
I want to create a custom gradle test task to only run JUNIT tests and omit Robolectric tests. I am attempting to achieve this task by creating a new test annotation and omitting any tests that include that new annotation.
Error
JUNIT packages are not included when I run the the gradle task.
error: package android.test.suitebuilder.annotation does not exist
import android.test.suitebuilder.annotation.SmallTest;
Details
New Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RobolectricTest {
}
Gradle File
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
repositories {
mavenCentral()
maven { url 'http://artifactory.ops.am1.qa.ext.bamgrid.com/artifactory/mobile-resources' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.gms:google-services:1.3.0-beta1'
}
}
android {
compileSdkVersion rootProject.ext.compileSDKVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.example"
minSdkVersion 17
targetSdkVersion rootProject.ext.targetSdkVersion
buildConfigField "String", "BUILD_TIME", "\"" + getDateAndTime() + "\""
buildConfigField "String", "VERSION_BUILD", "\"" + project["VERSION_BUILD"] + "\""
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
signingConfigs {
debug {
storeFile file(project.DEBUG_KEYSTORE)
storePassword project.DEBUG_KEYSTORE_PASSWORD
keyAlias project.DEBUG_KEYSTORE_ALIAS
keyPassword project.DEBUG_KEY_PASS
}
release {
storeFile file(project.RELEASE_KEYSTORE)
storePassword project.RELEASE_KEYSTORE_PASSWORD
keyAlias project.RELEASE_KEYSTORE_ALIAS
keyPassword project.RELEASE_KEY_PASS
}
}
sourceSets {
main {
res.srcDirs = ['src/main/res/',
'src/main/abc']
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
proguardFile 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
testCoverageEnabled = true
debuggable true
signingConfig signingConfigs.debug
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile ('junit:junit:4.12')
testCompile ('org.apache.maven:maven-ant-tasks:2.1.3')
testCompile ('org.robolectric:robolectric:3.0')
testCompile ('org.robolectric:shadows-support-v4:3.0')
}
sourceSets {
unitTest {
java.srcDirs = ['src/test/java']
resources.srcDirs = ['src/test/resources']
}
}
ClassLoader getClassLoader() {
List urls = sourceSets.test.runtimeClasspath.collect {
it.toURI().toURL()
}
return URLClassLoader.newInstance( urls as URL[] )
}
/**
* Filters out files that have specific annotation
* @param map - map of things to filter
* @return - list of acceptable files after filter
*/
List annotationFilter( Map map ) {
map.prefix = map?.prefix ?: '' // prefix: provide convenience for passing in annotation names
ClassLoader loader = classLoader
List result
// filter with annotations
if( !map.includes ) {
result = map?.names
} else {
result = []
map?.names.each { name ->
Class klass = loader.loadClass( name )
map?.includes.each { annotationName ->
String fullName = map.prefix + annotationName
Class annotation = loader.loadClass( fullName ).asSubclass( Annotation )
if( !klass.isAnnotationPresent( annotation ) ) {
result << name
}
}
}
}
if( result?.size() == 0 ) result = [ 'no.tests.to.run' ]
return result
}
/**
* Gradle task to run only robolectric tests.
*/
task unitTest( type: Test, description: 'Run all junit tests' ) {
android.sourceSets.main.java.srcDirs.each { dir ->
def buildDir = dir.getAbsolutePath().split('/')
buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')
sourceSets.unitTest.compileClasspath += files(buildDir)
sourceSets.unitTest.runtimeClasspath += files(buildDir)
}
testClassesDir = sourceSets.unitTest.output.classesDir
classpath = sourceSets.unitTest.runtimeClasspath
doLast {
println "Doing Last"
List names = testClassNames()
List filtered = annotationFilter( names: names, includes: ['testUtils.RobolectricTest'] )
println 'Running ' + filtered.size() + ' tests:\n' + filtered*.toString()*.replaceAll('^','\t').join('\n')
filter {
setIncludePatterns( filtered as String[] )
}
}
}
回答1:
Not so much Robolectric-specific, but in regards to declaring a custom test task for Android with Gradle, I ran into a lot of trouble with this. As you found, all of the documentation and examples are using the Java plugin, but the Android plugin subverts most of it.
The only solution I found with the Android plugin is to create another build type, which will then result, under the hood, in the Android plugin creating new test tasks for me. Then I can easily modify the tasks.
Here is the relevant setup, which I keep in a <rootProject>/gradle/test.gradle
file (This specific example uses Category annotation to filter unit tests into one task and integration tests into a separate task. For information on that part of it see https://github.com/junit-team/junit4/wiki/Categories):
android {
buildTypes {
integration
}
testOptions {
unitTests.all {
useJUnit()
if (it.name == 'testIntegrationUnitTest') {
options {
excludeCategories 'com.example.categories.UnitTest'
}
} else {
options {
excludeCategories 'com.example.categories.IntegrationTest'
}
}
}
}
}
Then the <module>/build.gradle
file applies from this file with apply from: "../gradle/test.gradle"
This causes the android
closure from the two files to be merged, results in a new integration
build type, which in turn results in a new testIntegrationUnitTest
task on the project in addition to the standard testDebugUnitTest
and testReleaseUnitTest
tasks.
But now, testDebugUnitTest
runs only "real" unit tests, while testIntegrationUnitTest
runs only integration tests.
Your mileage/implementation may vary. Once you're inside the unitTest.all
closure you can do whatever manipulation you need to the options.
回答2:
Custom Gradle Task To Only Run Specific Tests
Building on @alphonzo79 answer, I was able to solve the issue.
The things to know
- Android gradle omits common testing features in gradle, like exclude and custom sourceSets.
- Retention and Target was not helpful. Only categories worked.
- You don't need a new build type, you only need to change the test option when compiling your own tasks.
- Don't use this - https://github.com/pkainulainen/gradle-examples/blob/master/integration-tests/build.gradle
The complete answer was to create a custom task that changed a flag for the android testOptions to excludeCategories.
LINK
CODE
def integrationTests = false
...
testOptions {
unitTests.all {
useJUnit()
if (integrationTests.toBoolean()) {
println "Integration Tests Only for " + it.name
options {
excludeCategories 'com.example.reactivemvp.categories.UnitTest'
}
} else {
println "Unit Tests Only for " + it.name
options {
excludeCategories 'com.example.reactivemvp.categories.IntegrationTest'
}
}
}
}
...
task integrationTest(
type: Test,
description: 'Run integration tests only. Pass in \'-Pintegration=true\'',
dependsOn: ['testDebugUnitTest', 'clean'] ) {
//Here for task completion, not actually used since sub task of testDebugUnitTest
testClassesDir = file("src/integrationTest/java/");
classpath = files("$System.env.ANDROID_HOME/sources/android-18")
//
//Turn on integration testing when argument exists and is true
//
if (project.hasProperty('integration')) {
println integration
if (integration == 'true') {
integrationTests = true
}
}
}
回答3:
You said Robolectric can't find AndroidManifest.xml when testing multidimensional flavor project on Ubuntu
try to explicitly give the path to your manifest file in the @Config annotation
@Config(constants = BuildConfig.class, manifest = "../<path to>/AndroidManifest.xml")
回答4:
We can use the following configuration to exclude multiple tests by name:
def integrationTests = false
android {
//...
testOptions {
unitTests {
includeAndroidResources = true
returnDefaultValues = true
all {
test {
filter {
if (integrationTests.toBoolean()) {
includeTestsMatching "*IntegrationTest"
} else {
includeTestsMatching "*UnitTest"
}
}
}
}
}
}
}
In command line:
gradlew test -->Run only *UnitTest
来源:https://stackoverflow.com/questions/31819127/custom-gradle-test-task-for-android