使用注释处理生成单元测试

时间:2016-06-02 22:42:55

标签: java android-studio junit annotation-processing

我一直在寻找有关此事的信息,但我找不到任何有用的资源。

我需要使用注释处理生成单元测试。生成一个可以作为单元测试的类没有问题。我不知道怎么做的是将这些生成的文件放在正确的文件夹中。

默认情况下,输出将位于build/generated/source/apt/debug文件夹中,但我需要将这些文件放在build/generated/source/apt/test上。我猜。我的意思是我在注释处理之前使用过,但我从来没有用过生成单元测试,因此我不知道在哪里或如何定位它们的正确方法是什么。

顺便说一下,我正在使用Android Studio 2.0。

3 个答案:

答案 0 :(得分:2)

man page

  

-s dir指定放置生成的源文件的目录。该目录必须已存在; javac不会创建它。如果是一个班级   是包的一部分,编译器将源文件放在一个包中   反映包名的子目录,创建目录为   需要。例如,如果指定-s / home / mysrc且类是   调用com.mypackage.MyClass,然后将源文件放入   /home/mysrc/com/mypackage/MyClass.java。

我认为这就是你要找的东西。

但是如果你的一些注释正在生成应该放在一个目录中的单元测试,并且你的一些注释正在生成应该放在另一个目录中的生产代码,那么我认为这个解决方案将不起作用。

答案 1 :(得分:1)

另一个选择是编写一个简单的Gradle插件,根据您的需要配置项目。通过编写自己的插件,您可以配置所需的一切,例如为注释处理器添加依赖项,然后修改javaCompile任务以将生成的依赖项移动到所需的文件夹。

现在我意识到这可能看起来过分,但是Gradle插件功能非常强大且非常容易制作。如果你可以克服编写Groovy代码的初始学习曲线(我假设你在build.gradle文件中没有使用Groovy),那么它可以是一个非常快速和简单的选项来做你想做的事情

在我开始解释如何将Gradle插件与您的库结合使用之前,让我解释一下我在做什么:

我曾经写过一个名为ProguardAnnotations的库,它需要比单独使用注释处理器做得更多。在我的情况下,我需要配置项目的proguard设置,以使用由我的注释处理器生成的proguard规则文件。实现插件并没有太多工作,除了配置proguard设置之外,我还可以使用它将我的注释处理器的依赖项添加到项目中。然后我将插件发布到Gradle插件存储库,所以现在使用我的插件,它将添加所有必需的依赖项并适当地配置项目所有用户必须做的是这是他们的build.gradle文件的顶部:

plugins {
    id "com.github.wrdlbrnft.proguard-annotations" version "0.2.0.51"
}

因此,您可以看到这如何使您的库使用变得非常简单。只需添加这个Gradle就可以发挥其魔力并处理所有插件配置。

现在让我们看一下插件本身。作为参考this link将带您到我为我的库写的Gradle插件。你的插件最终应该看起来非常相似。

让我们先看看项目结构,为了简单起见,我将向您展示我为我的库编写的Gradle插件的截图。这应该是Gradle插件所需的最简单的设置:

[Gradle Plugin Project Setup][1]

这里有三个重要的部分。 Gradle使用Groovy作为其选择的脚本语言。所以你需要做的第一件事是在这里获取Groovy SDK:http://groovy-lang.org/download.html

我建议您使用IntelliJ编写Gradle插件,但理论上Android Studio应该可以使用一些额外的配置。

由于我们正在编写groovy代码,因此您需要将代码放在src/main/groovy文件夹而不是src/main/java中。您的源文件本身需要.groovy扩展名而不是.java。 IntellIj在这里非常棘手,因为即使你在src/main/groovy文件夹中工作,它仍然总是主要提示你创建java文件,只需要注意文件名旁边的图标的形状。如果它是方形而不是圆形,那么你正在处理一个groovy文件。除了编写Groovy代码是非常简单的 - 每个有效的Java代码在Groovy中也是有效的 - 所以你可以开始编写像Java中习惯的代码,它将编译。对于初学者,我不建议使用所有其他Groovy功能,因为它可能非常混乱。

另一个非常重要的部分是资源文件夹。在屏幕截图中,您可以在文件夹src/main/resources/META-INF/gradle-plugins中看到属性文件。此属性文件决定了Gradle插件的id(实际上是名称)。它本质上非常简单:属性文件的名称是Gradle插件的名称!屏幕截图中的属性文件名为com.github.wrdlbrnft.proguard-annotations.properties,因此我的Gradle插件的名称为com.github.wrdlbrnft.proguard-annotations。如果您想在build.gradle文件中应用它,您可以在apply语句中使用该名称:apply project: 'com.github.wrdlbrnft.proguard-annotations'或上面id部分中的plugins进一步显示!

最后一部分是build.gradle本身。您需要将其配置为能够编译groovy代码,并且您需要Gradle插件所需的所有依赖项。幸运的是,您只需要五行代码:

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

在build.gradle中使用这个基本设置,并且可能会略微摆弄你的IDE设置,你应该准备好编写自己的Gradle插件。

现在让我们自己创建插件类。选择Java中的包名称并创建适当的groovy文件,例如YourLibraryPlugin.groovy。 Gradle插件的基本样板如下所示:

package com.github.example.yourlibrary

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.ProjectConfigurationException

/**
 * Created by Xaver Kapeller on 11/06/16.
 */
class YourLibraryPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {

    }
}

现在,与Java相比,Groovy代码中有两个不同的东西:

  • 您不需要指定课程的可见性。在Java代码中没有指定任何包本地可见性通常是最佳选择。但是,如果需要,您可以指定公共可见性,不做任何更改。
  • 如果查看导入,可以看到每行末尾没有分号。在Groovy中,分号纯粹是可选的。你在任何地方都不需要它们。但是也没关系。它们不是必需的。

该类本身是您的主要插件类。它是你的插件开始发挥其魔力的地方。插件应用于项目后立即调用apply(Project)方法。如果想要详细了解build.gradle文件中的apply plugin: 'com.android.application'语句,那么现在就有了答案。它们创建插件类的实例,并使用Gradle项目作为参数调用apply方法。

通常,您在apply方法中要做的第一件事是:

@Override
void apply(Project project) {
    project.afterEvaluate {

    }
}

现在project.afterEvaluate表示在评估整个build.gradle之后调用afterEvaluate后括号内的代码。这是一件好事,因为您的插件可能依赖于应用于项目的其他插件,但开发人员可能会在引用您的插件的语句apply project: ...之后放置apply project: ...语句。因此,在其他方面,通过调用afterEvaluate,您确保在执行任何操作之前至少已经发生了基本项目配置,这可以避免错误并减少使用插件的开发人员的摩擦。但你不应该过度。您可以立即配置有关项目的所有内容。但是在你的情况下,现在没有什么可做的,所以我们继续afterEvaluate声明。

您现在可以做的第一件事就是为注释处理器添加依赖项。这意味着您的用户只需要应用插件,而不必担心自己添加任何依赖项。

@Override
void apply(Project project) {
    project.afterEvaluate {
        project.afterEvaluate {

            project.dependencies {
                compile 'com.github.wrdlbrnft:proguard-annotations-api:0.2.0.44'
                apt 'com.github.wrdlbrnft:proguard-annotations-processor:0.2.0.44'
            }
        }
    }
}

将依赖项添加到项目中就像在build.gradle文件中一样。您可以看到我在这里使用apt分类器作为注释处理器。您的用户需要将apt插件也应用于项目才能使其正常工作。但是我作为练习留给你的是你还可以检测apt插件是否已经应用到项目中,以及它是否已经自动应用它!您的Gradle插件可以为您的用户提供另一件事。

现在让我们来看看你希望你的Gradle插件做的事情。在最基本的层面上,您需要做一些事情来响应您的注释处理器完成单元测试的创建。

因此,我们需要做的第一件事是弄清楚我们正在使用哪种项目。它是android库项目还是android应用程序项目?这对于一个复杂的原因很重要,我不会在这个答案中解释,因为它会使这个已经很久的答案长得多。我将向您展示代码并基本解释它的作用:

@Override
void apply(Project project) {
    project.afterEvaluate {
        project.afterEvaluate {

            project.dependencies {
                compile 'com.github.wrdlbrnft:proguard-annotations-api:0.2.0.44'
                apt 'com.github.wrdlbrnft:proguard-annotations-processor:0.2.0.44'
            }

            def variants = determineVariants(project)

            project.android[variants].all { variant ->
                configureVariant(project, variant)
            }
        }
    }
}

private static String determineVariants(Project project) {
    if (project.plugins.findPlugin('com.android.application')) {
        return 'applicationVariants';
    } else if (project.plugins.findPlugin('com.android.library')) {
        return 'libraryVariants';
    } else {
        throw new ProjectConfigurationException('The com.android.application or com.android.library plugin must be applied to the project', null)
    }
}

它的作用是检查是否已应用com.android.library插件或com.android.application插件,然后针对此案例迭代项目的所有变体。这意味着您在build.gradle中指定的基本上所有项目风格和buildType都是独立配置的 - 因为它们本质上也是不同的构建过程,需要自己的配置。 def类似于C#中的var关键字,可用于声明变量而不明确指定类型。

project.android[variants].all { variant ->
    configureVariant(project, variant)
}

这部分循环遍历所有不同的变体,然后调用configureVariant方法。在这种方法中,所有的魔法都发生了,这是你项目的重要部分。我们来看看基本实现:

private static void configureVariant(Project project, def variant) {
    def javaCompile = variant.hasProperty('javaCompiler') ? variant.javaCompiler : variant.javaCompile
    javaCompile.doLast {

    }
}

现在,该方法的第一行是一个有用的片段,它基本上做了一件事:它返回java编译任务。我们需要这个,因为注释处理是java编译过程的一部分,一旦编译任务完成,那么注释处理器也已完成。 javaCompile.doLast {}部分类似于afterEvaluate。它允许我们在任务结束时使用我们自己的代码!因此,在java编译任务之后,注释处理完成了doLast执行后括号内的部分!在那里,您现在终于可以为您的项目做您需要做的事情。由于我并不完全知道你需要做什么或者你需要做什么,所以我只想举个例子:

private static void configureVariant(Project project, def variant) {
    def javaCompile = variant.hasProperty('javaCompiler') ? variant.javaCompiler : variant.javaCompile
    javaCompile.doLast {
        def generatedSourcesFolder = new File(project.buildDir, 'generated/apt')
        def targetDirectory = new File(project.buildDir, 'some/other/folder');
        if(generatedSourcesFolder.renameTo(targetDirectory)) {
            // Success!!1 Files moved.
        }
    }
}

就是这样!虽然这是一个相当长的答案,但它只触及整个主题的表面,所以如果我忘记了重要的事情,或者你有任何进一步的问题随时可以问。

然而最后一件事:

如果您需要将生成的文件移动到其他文件夹,则需要注意apt文件夹中可能存在许多其他库中的其他生成文件,通常将它们移开是件好事。 。因此,您需要找出一个系统来过滤文件夹中的文件 - 例如一些常见的前缀或后缀。这不应该是一个问题。

我需要提及的另一件事是:一旦你在javaCompile方法中掌握了configureVariants()任务,你就可以实际为注释处理器指定命令行参数,如@emory所提到的。然而,这可能非常棘手。事实上,这正是android-apt插件的作用。它通过在build/generated/apt任务上指定javaCompile文件夹作为所有注释处理器的输出文件夹。你再也不想搞砸了。我不知道如何为一个注释处理器指定输出文件夹 - 即你的 - 但可能有一种方法。如果你有时间,你可能想要调查它。您可以查看android-apt here的相关源代码。处理器输出路径的指定发生在configureVariants方法的下方。

在build.gradle中设置Gradle插件项目与任何其他Gradle项目非常相似,实际上非常简单。但是作为参考,这里是我用于我写的Gradle插件的完整build.gradle。如果您需要帮助来确定如何设置将插件发布到jcenter或Gradle Plugin Pepository,或者只是任何一般配置,您可以从中查看:

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        jcenter()
    }
    dependencies {
        classpath "com.gradle.publish:plugin-publish-plugin:0.9.4"
        classpath 'com.novoda:bintray-release:0.3.4'
    }
}

apply plugin: "com.gradle.plugin-publish"
apply plugin: 'com.jfrog.bintray'
apply plugin: 'maven-publish'
apply plugin: 'maven'
apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

final bintrayUser = hasProperty('bintray_user') ? property('bintray_user') : ''
final bintrayApiKey = hasProperty('bintray_api_key') ? property('bintray_api_key') : ''
final versionName = hasProperty('version_name') ? property('version_name') : ''

version = versionName

pluginBundle {
    vcsUrl = 'https://github.com/Wrdlbrnft/ProguardAnnotations'
    website = 'https://github.com/Wrdlbrnft/ProguardAnnotations'
    description = 'Makes dealing with Proguard simple and easy!'
    plugins {

        ProguardAnnotationsPlugin {
            id = 'com.github.wrdlbrnft.proguard-annotations'
            displayName = 'ProguardAnnotations'
            tags = ['android', 'proguard', 'plugin']
        }
    }
}

task sourcesJar(type: Jar, dependsOn: classes) {
    classifier = 'sources'
    from sourceSets.main.allSource
}

publishing {
    publications {
        Bintray(MavenPublication) {
            from components.java
            groupId 'com.github.wrdlbrnft'
            artifactId 'proguard-annotations'
            artifact sourcesJar
            version versionName
        }
    }
}

bintray {
    user = bintrayUser
    key = bintrayApiKey
    publications = ['Bintray']
    pkg {
        repo = 'maven'
        name = 'ProguardAnnotationsPlugin'
        userOrg = bintrayUser
        licenses = ['Apache-2.0']
        vcsUrl = 'https://github.com/Wrdlbrnft/ProguardAnnotations'
        publicDownloadNumbers = true
        version {
            name = versionName
            released = new Date()
        }
    }
}

如果您对build.gradle文件中未定义的所有三个或四个变量感到困惑 - 那些在我运行构建时由构建服务器注入。它们会在开发时自动回退到某些默认值。

我希望我能帮助你让你的图书馆变得惊人:)

答案 2 :(得分:0)

使用android apt-plugin解决方案是使用testApt而不是apt,就像在issue上建议的那样。

然而,这提出了限制将要处理的类的范围限制到当前测试环境的限制,这不是我所需要的,但对大多数用户来说可能没问题。