在本文中,我分享了为自定义Gradle插件创建功能测试以及如何配置插件项目以从测试中收集代码覆盖率指标的经验。
在上一篇文章中,我描述了如何构建自定义Gradle插件。在这里,我们将继续与之合作。在开始之前,建议您重新编写上一篇文章,以更好地了解我们的起点。
0.克隆项目
git clone -b chapter-1 https://github.com/SurpSG/code-lines-counter-gradle-plugin.git
1.配置 创建一个单独的源集,其中将包含功能测试。首先创建一个简单目录src/funcTests/kotlin。
src/funcTests/kotlin
项目文件结构创建后,该目录看起来像一个普通文件夹,并且未被IDE识别为代码源。
现在该向Gradle解释该目录将包含代码,或者换句话说,使其成为“ sourceSet”。
打开项目根目录中的“ build.gradle ”文件,添加新的源集配置,然后重新导入项目:
sourceSets { functionalTest { // declares a new sourceset // specifies code source dir kotlin.srcDir file('src/funcTests/kotlin') // specifies resource dir resources.srcDir file('src/funcTests/resources') // specifies dependencies to compile test classes compileClasspath += sourceSets.main.output + configurations.testRuntimeClasspath // specifies dependencies to run tests runtimeClasspath += output + compileClasspath } }
创建一个将运行功能测试的Gradle任务,并为测试配置添加Kotlin std lib依赖项:
task functionalTest(type: Test) { description = 'Runs the functional tests.' group = 'verification' testClassesDirs = sourceSets.functionalTest.output.classesDirs classpath = sourceSets.functionalTest.runtimeClasspath } check.dependsOn functionalTest
2.测试创建 您可以使用任何您喜欢的测试框架。在此示例中,我使用JUnit 4.12。创建com.github.CodeLinesPluginTest Kotlin类:
class CodeLinesPluginTest { // creates temp directory for a gradle project <-------- (1) @get:Rule var testProjectDir = TemporaryFolder() private lateinit var buildFile: File private lateinit var gradleRunner: GradleRunner @Before fun setup() { // creates empty build.gradle file in the test gradle project <-------- (2) buildFile = testProjectDir.newFile("build.gradle") // creates and configures gradle runner <-------- (3) gradleRunner = GradleRunner.create() .withPluginClasspath() .withProjectDir(testProjectDir.root) .withTestKitDir(testProjectDir.newFolder()) } @Test fun `check test setup`() { // runs `tasks` gradle task <-------- (4) val result = gradleRunner .withArguments("tasks") .build() println(result.output) } }
在上面的示例中,这是一个简单的功能测试。该测试创建了一个摇篮工程,并运行任务的摇篮任务。让我们一步一步地探索测试:
任务的
应用“代码行”插件:
@Before fun setup() { buildFile = testProjectDir.newFile("build.gradle") // add common configuration for all tests in this class buildFile.appendText(""" plugins { id 'java' // `code-lines` plugin is dependent on `java` plugin id 'com.github.code-lines' } """.trimIndent()) ... }
然后更新测试:
@Test fun `codeLines task should print '0' when there is no source code`() { val result = gradleRunner .withArguments("codeLines") // <------- (1) .build() assertEquals(SUCCESS, result.task(":codeLines")!!.outcome) // <------- (2) assertTrue(result.output.contains("Total lines: 0")) // <------- (3) }
现在,测试执行以下步骤:
添加一个Java类以验证插件正确计数行数。 应用非默认插件的配置。 按位置创建一个简单的Java类 code-lines-counter-gradle-plugin/src/funcTests/resources/TestClass.java
code-lines-counter-gradle-plugin/src/funcTests/resources/TestClass.java
public class TestClass { public static void main(String[] args) { System.out.println("Hello world"); } }
添加一个新测试:
@Test fun `codeLines task should print 'Total lines 6'`() { // creates folders in the temp project val testClassLocation: File = testProjectDir.newFolder("src", "main", "java").resolve("TestClass.java") CodeLinesPluginTest::class.java.classLoader .getResource("TestClass.java")!!.file.let(::File) .copyTo(testClassLocation) // copies test file from resources to test project val result = gradleRunner .withArguments("codeLines") .build() assertEquals(SUCCESS, result.task(":codeLines")!!.outcome) assertTrue(result.output.contains("Total lines: 6")) }
再添加一个测试以检查插件的配置:
@Test fun `codeLines should skip blank lines`() { val testClassLocation: File = testProjectDir.newFolder("src", "main", "java").resolve("TestClass.java") CodeLinesPluginTest::class.java.classLoader .getResource("TestClass.java")!!.file.let(::File) .copyTo(testClassLocation) // apply `code-lines` plugin configuration buildFile.appendText(""" codeLinesStat { sourceFilters.skipBlankLines = true } """.trimIndent()) val result = gradleRunner .withArguments("codeLines") .build() assertEquals(SUCCESS, result.task(":codeLines")!!.outcome) assertTrue(result.output.contains("Total lines: 5")) }
3.代码覆盖率 应用Jacoco插件。打开build.gradle并使用以下命令进行更新:
plugins { id 'jacoco' } jacocoTestReport { reports.html.enabled = true executionData.setFrom fileTree(buildDir).include("/jacoco/*.exec") // <------ (1) } jacoco { toolVersion = '0.8.5' // <----- (2) }
指定要分析的覆盖率数据文件。因为我们有两个单独的测试源集,所以我们需要告诉Jacoco。设置Jacoco版本。我更喜欢最新版本,尤其是当我使用Kotlin时。
在这一步,我们应该添加一些额外的配置。使用TestKit执行的测试在守护程序JVM中运行。这就是为什么我们需要告诉守护程序JVM使用Jacoco Java代理。
幸运的是,我们可以使用Jacoco-gradle-testkit-plugin在这里为我们提供帮助:
Jacoco-gradle-testkit-plugin
plugins { id "pl.droidsonroids.jacoco.testkit" version "1.0.5" } functionalTest.dependsOn generateJacocoTestKitProperties generateJacocoTestKitProperties.destinationFile = file("$buildDir.absolutePath/jacoco/functional.exec")
在CodeLinesPluginTest 类中更新GradleRunner配置:
CodeLinesPluginTest
gradleRunner = GradleRunner.create() .withPluginClasspath() .withProjectDir(testProjectDir.root) .withTestKitDir(testProjectDir.newFolder()) .apply { // gradle testkit jacoco support File("./build/testkit/testkit-gradle.properties") .copyTo(File(projectDir, "gradle.properties")) }
现在,我们可以运行测试并检查覆盖率:
gradlew check jacocoTestReport
在您喜欢的浏览器中打开{PROJECT-ROOT}/build/reports/jacoco/test/html/index.html
{PROJECT-ROOT}/build/reports/jacoco/test/html/index.html
我建议您在项目中再使用一个插件。DiffCoverage 基于新的或修改的代码构建覆盖报告。这可能有助于保持较高的代码覆盖率。
完整的工作示例跟随链接或克隆项目:
git clone -b chapter-2-testing https://github.com/SurpSG/code-lines-counter-gradle-plugin.git
结论 功能测试很重要,因为它们会告诉您插件是否正常工作。您可以检查插件,但不要过度创建测试,因为它们在性能方面非常差。首选常见的快乐路径方案,例如检查插件的配置,插件的任务生命周期,与其他插件的交互。
代码覆盖率工具有助于检测未经测试的代码,因此潜在地,您可以在提交代码之前发现并修复缺陷。不要完全依赖此类工具,因为它们不能保证涵盖所有极端情况。它们只是告诉您是否在测试运行时调用了代码。
原文链接:http://codingdict.com/