【问题标题】:Custom Gradle Test Task For Android适用于 Android 的自定义 Gradle 测试任务
【发布时间】:2015-08-04 20:53:14
【问题描述】:

问题

我想创建一个自定义 gradle 测试任务以仅运行 JUNIT 测试并省略 Robolectric 测试。我试图通过创建一个新的测试注释并省略包含该新注释的任何测试来完成此任务。

错误

运行 gradle 任务时不包含 JUNIT 包。

error: package android.test.suitebuilder.annotation does not exist
import android.test.suitebuilder.annotation.SmallTest;

详情

新注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RobolectricTest {
}

Gradle 文件

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[] )
        }
    }
}

【问题讨论】:

  • Robolectric 测试是单元测试。你really想做什么?
  • 100% 正确。 Robolectric 不在我们的 Linux 服务器上运行。我想把它们删掉,只做 JUNIT。
  • Robolectric isn't run on our Linux servers. 什么? Robolectric 在 Linux 上运行良好。你没有问正确的问题。不要因为您认为它们不在 Linux 上运行而中断您的测试工作。
  • Robolectric 在 Ubuntu 上测试多维风味项目时找不到 AndroidManifest.xml github.com/robolectric/robolectric/issues/1936

标签: android unit-testing gradle robolectric


【解决方案1】:

与其说是 Robolectric 特定的,但在使用 Gradle 为 Android 声明自定义测试任务时,我遇到了很多麻烦。如您所见,所有文档和示例都使用 Java 插件,但 Android 插件颠覆了大部分内容。

我在 Android 插件中找到的唯一解决方案是创建另一种构建类型,然后在底层,Android 插件会为我创建新的测试任务。然后我就可以轻松修改任务了。

这是相关设置,我将其保存在 &lt;rootProject&gt;/gradle/test.gradle 文件中(此特定示例使用 Category 注释将单元测试过滤到一个任务中,并将集成测试过滤到一个单独的任务中。有关该部分的信息,请参阅 @987654321 @):

android {

    buildTypes {
        integration
    }

    testOptions {
        unitTests.all {
            useJUnit()

            if (it.name == 'testIntegrationUnitTest') {
                options {
                    excludeCategories 'com.example.categories.UnitTest'
                }
            } else {
                options {
                    excludeCategories 'com.example.categories.IntegrationTest'
                }
            }
        }
    }
}

然后&lt;module&gt;/build.gradle 文件从此文件应用apply from: "../gradle/test.gradle"

这会导致两个文件中的 android 闭包被合并,从而产生一个新的 integration 构建类型,这反过来又导致项目上除了标准的 testDebugUnitTest 之外还有一个新的 testIntegrationUnitTest 任务和testReleaseUnitTest 任务。

但现在,testDebugUnitTest 只运行“真正的”单元测试,而testIntegrationUnitTest 只运行集成测试。

您的里程/实施可能会有所不同。进入 unitTest.all 闭包后,您可以对选项进行任何操作。

【讨论】:

  • 此描述中是否缺少某些内容?当我只添加集成 buildType 时,集成构建没有任何反应。
  • 这个解决方案最适合我 - 谢谢。为了完整性,并为了他人的利益,我创建了一个 test-categories 存储库,这样我就不必为我的每个项目剪切粘贴内容
【解决方案2】:

自定义 Gradle 任务仅运行特定测试

基于@alphonzo79 的回答,我能够解决问题。

要知道的事情

完整的答案是创建一个自定义任务,将 android testOptions 的标志更改为 excludeCategories。

LINK

代码

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
        }
    }
  }

【讨论】:

  • 那你怎么关掉flag呢?
【解决方案3】:

我们可以使用如下配置按名称排除多个测试:

def integrationTests = project.hasProperty('integrationTests') ? project.getProperty('integrationTests') : false //Default value false
android {
    //...
    testOptions {
        unitTests {
            includeAndroidResources = true
            returnDefaultValues = true
            all {                
                test {
                    filter {
                        if (integrationTests.toBoolean()) {
                            includeTestsMatching "*IntegrationTest"
                        } else {
                            includeTestsMatching "*UnitTest"
                        }
                    }
                }
            }
        }
    }
}

在命令行中:

gradlew test // Run only *UnitTest, Default value is false.
gradlew test -PintegrationTests=false // Run only *UnitTest
gradlew test -PintegrationTests=true // Run only *IntegrationTest

【讨论】:

  • 所以你改变了构建文件中的布尔值,然后点击 gradlew test?这太荒谬了
  • 嗨@urSus,我更新了响应以处理参数,这样您就可以从命令行定义行为。
【解决方案4】:

你说 Robolectric 在 Ubuntu 上测试多维风味项目时找不到 AndroidManifest.xml

尝试在 @Config 注释中明确给出清单文件的路径

@Config(constants = BuildConfig.class, manifest = "../<path to>/AndroidManifest.xml")

【讨论】:

  • 升级版似乎解决了多维风味问题。
猜你喜欢
  • 2020-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-17
  • 1970-01-01
  • 2015-05-22
相关资源
最近更新 更多