Slide 1

Slide 1 text

Gradle Summit Testing the build with TestKit
 Benjamin Muschko 2016

Slide 2

Slide 2 text

About to ship code to production…

Slide 3

Slide 3 text

…but the build logic contained bugs

Slide 4

Slide 4 text

Why not test build logic?

Slide 5

Slide 5 text

Testing build logic Build logic needs to be testable on multiple levels.

Slide 6

Slide 6 text

Writing unit tests in Gradle Class under test does not use Gradle API Test logic in isolation Testing a single class

Slide 7

Slide 7 text

Example: Writing a unit test with Spock 
 package com.bmuschko.gradle.docker.tasks.image
 
 import spock.lang.Specification
 
 import static com.bmuschko.gradle.docker.tasks.image.Dockerfile.*
 
 class DockerfileTest extends Specification {
 def "Instruction String representation is built correctly"() {
 expect:
 instructionInstance.keyword == keyword
 instructionInstance.build() == builtInstruction 
 where:
 instructionInstance | keyword | builtInstruction
 new FromInstruction('ubuntu:14.04') | 'FROM' | 'FROM ubuntu:14.04'
 new FromInstruction({ 'ubuntu:14.04' }) | 'FROM' | 'FROM ubuntu:14.04'
 ...
 }
 }

Slide 8

Slide 8 text

Writing integration tests in Gradle Class(es) under test use Gradle API Usually interacts with Project instance Does not execute a full build script

Slide 9

Slide 9 text

Example: Writing an integration test using ProjectBuilder 
 import org.gradle.api.Project
 import org.gradle.testfixtures.ProjectBuilder class DockerJavaApplicationPluginIntegrationTest extends Specification {
 @Rule TemporaryFolder temporaryFolder = new TemporaryFolder()
 Project project
 
 def setup() {
 project = ProjectBuilder.builder().withProjectDir(temporaryFolder.root).build()
 }
 
 def "Creates tasks out-of-the-box when application plugin is applied"() {
 when:
 project.apply(plugin: DockerJavaApplicationPlugin)
 project.apply(plugin: 'application')
 
 then: project.tasks.findByName(DockerJavaApplicationPlugin .COPY_DIST_RESOURCES_TASK_NAME)
 project.tasks.findByName(DockerJavaApplicationPlugin.DOCKERFILE_TASK_NAME)
 project.tasks.findByName(DockerJavaApplicationPlugin.BUILD_IMAGE_TASK_NAME)
 project.tasks.findByName(DockerJavaApplicationPlugin.PUSH_IMAGE_TASK_NAME)
 }
 }

Slide 10

Slide 10 text

Writing functional tests in Gradle Executes build script similar to end user Examines build outcome and output Isolated test environment

Slide 11

Slide 11 text

Example: Writing a functional test with TestKit 
 def "can successfully create Dockerfile"() {
 given:
 buildFile << """
 import com.bmuschko.gradle.docker.tasks.image.Dockerfile
 
 task dockerfile(type: Dockerfile) {
 from 'ubuntu:14.04'
 maintainer 'Benjamin Muschko "[email protected]"'
 } """
 
 when:
 def result = GradleRunner.create()
 .withProjectDir(testProjectDir.root)
 .withArguments('dockerfile')
 .build()
 
 then:
 result.task(":dockerfile").outcome == SUCCESS testProjectDir.file('Dockerfile').exists()
 }

Slide 12

Slide 12 text

What’s the Gradle TestKit? - Uses Tooling API as test execution harness - Agnostic of test framework - Assertions made based on build output, build logging or executed tasks + their result Functional testing support in Gradle core. Gradle 2.6

Slide 13

Slide 13 text

TestKit usage Declaring TestKit dependency 
 dependencies {
 testCompile gradleTestKit()
 }
 Declaring dependency on test framework 
 dependencies {
 testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
 }

Slide 14

Slide 14 text

DEMO Functional testing of a build script

Slide 15

Slide 15 text

Cross-version compatibility tests Forward and backward compatibility independent of Gradle version used to build logic. 2.4 2.5 2.6 2.3 2.2 Version used for writing build logic

Slide 16

Slide 16 text

Retrieving Gradle distributions Enterprise Server Local or Shared Disk Build Machine Public Server

Slide 17

Slide 17 text

Specifying a Gradle version Using a Gradle version available on server Using a Gradle installation on disk Using a Gradle distribution via URL GradleRunner.withGradleVersion(String) GradleRunner.withGradleInstallation(File) GradleRunner.withGradleDistribution(URI)

Slide 18

Slide 18 text

DEMO Cross-version compatibility testing

Slide 19

Slide 19 text

Testing a plugin - Tooling API runs in separate process - Does not share classpath and classloaders as test process - Requires injection of code under test Requires additional work!

Slide 20

Slide 20 text

DEMO Manually injecting the plugin classpath

Slide 21

Slide 21 text

That’s some complex machinery!

Slide 22

Slide 22 text

Simplifying setup with plugin development plugin - Declaration of gradleApi() and gradleTestKit() - Generates plugin classpath manifest file - Plugin classpath injection needs to be declared for GradleRunner instance explicitly Apply sensible default and conventions…

Slide 23

Slide 23 text

Automatic injection of plugin code Applying the Java Gradle plugin dev. plugin 
 apply plugin: 'java-gradle-plugin' Injecting classpath into GradleRunner 
 GradleRunner.create()
 .withPluginClasspath()
 .build() Gradle 2.13

Slide 24

Slide 24 text

DEMO Testing a plugin with TestKit

Slide 25

Slide 25 text

Configurable conventions Source set containing code under test Source set used for injecting the plugin classpath Reconfigurable with the help of the class GradlePluginDevelopmentExtension sourceSets.main sourceSets.test

Slide 26

Slide 26 text

DEMO Configuring a dedicated test source set

Slide 27

Slide 27 text

Debugging test execution Setting system property for ad-hoc testing Enabling debugging programmatically -Dorg.gradle.testkit.debug=true GradleRunner.withDebug(true)

Slide 28

Slide 28 text

DEMO Debugging tests from the IDE

Slide 29

Slide 29 text

Feature limitations Support for features determined by version of Tooling API used to execute test. More info: https:/ /docs.gradle.org/current/userguide/test_kit.html

Slide 30

Slide 30 text

Future enhancements for TestKit - Convenience test fixtures - Hooking into IDE plugins - Integration with JaCoCo plugin More info: https://github.com/gradle/gradle/blob/master/ design-docs/testing-toolkit.md

Slide 31

Slide 31 text

Thank You! Please ask questions… https:/ /www.github.com/bmuschko @bmuschko

Slide 32

Slide 32 text

Introducing Gradle Cloud Services - Insights into your build - View and share via URL - Debug, optimize and refine - Analyze all of your builds Stop by the Gradle Lounge and create a Build Scan! The first service, Gradle Build Scans, is now available.

Slide 33

Slide 33 text

Who Are We Motto: Build Happiness Mission: To revolutionize the way software is built and shipped. We exist to end once-and-for-all the worst things about big software and restore the reason you got into coding in the first place. We’re Hiring: Gradle is hiring front-end, back-end, and core software engineers. Visit gradle.org/jobs to apply

Slide 34

Slide 34 text

Learn more at www.gradle.org