Skip to content

Integrating with Gradle

The Teamscale Gradle Plugin enables seamless integration between your Gradle builds and Teamscale, allowing you to upload test reports, coverage data, and leverage Test Impact Analysis (TIA) capabilities. The plugin also provides utilities for aggregating and compacting reports to reduce their storage footprint.

It is split into two cooperating pieces.

  • The com.teamscale plugin, applied to each project, instruments individual test tasks: it attaches a coverage agent to the JVM, collects execution data per test, and optionally enables Test Impact Analysis so only tests affected by recent changes are run. It also exposes TeamscaleUpload tasks that send those reports to the Teamscale server.
  • The com.teamscale.aggregation plugin, applied once at a project that depends on all projects from which data should be aggregated (e.g. the root project or the application project), acts as a collection point: it pulls the coverage and JUnit XML produced by all projects it depends on (also transitively), merges them into a single report per test suite before they are uploaded.

Option Reference

For the full task and property reference, see our Gradle plugin reference.

Common Scenarios

Single project

If you only have a single Gradle project with no subprojects, follow the Setup section only.

Application project with many library sibling projects

If you have a root project with many subprojects, one of which is the main application and the others are libraries the application depends on, follow the Setup section, then also follow Aggregation for Multi-Project Builds and apply com.teamscale.aggregation to the application project.

Root project with many subprojects

If you have a root project with many subprojects, follow the Setup section, then also follow Aggregation for Multi-Project Builds and apply com.teamscale.aggregation to the root project.

Separate aggregation project

If you already have a separate subproject that does not contain code but solely handles e.g. report publishing, follow the Setup section, then also follow Aggregation for Multi-Project Builds and apply com.teamscale.aggregation to that project. Make sure that this project has a dependency on all projects from which data should be aggregated.

Setup

Apply the Plugin

Add the Teamscale plugin to your build scripts (latest version). You must add it to each project that runs tests for which you want coverage or test results in Teamscale.

groovy
plugins {
	id "com.teamscale" version "35.0.2"
}

Configure the teamscale Extension

The teamscale extension configures the connection to Teamscale. Configure once in the project that should perform the report upload to Teamscale, e.g. the root project.

groovy
teamscale {
	server {
		url = 'https://mycompany.teamscale.io/'
		project = 'my-project'
		userName = 'build'
		userAccessToken = property('teamscale.access-token')
	}
}

By default, the plugin tries to detect the commit from CI environment variables (supported environment variables) or the local Git repository. If you need to manually specify a commit or baseline, see the full options reference. If auto-detection fails (e.g., building outside a Git repository without CI environment variables set), the upload will fail. In that case, set commit.revision explicitly.

Upload Reports

Register a TeamscaleUpload task to send test results and coverage to Teamscale.

groovy
import com.teamscale.TeamscaleUpload

tasks.register('teamscaleTestUpload', TeamscaleUpload) {
	partition = "Unit Tests"
	from(tasks.test)
	from(tasks.jacocoTestReport)
}

Run the Upload Task

Run the upload task to execute your tests and send the results to Teamscale:

bash
./gradlew teamscaleTestUpload --continue

We recommend using --continue if you have multiple projects and want test results/coverage even if the tests of one project fail.

The upload task depends on the test and coverage tasks, so they run automatically. After the build completes, the data is available in Teamscale:

  • Test results: visible in the Metrics > Tests perspective of your project
  • Coverage: visible in the Test Gaps and Metrics > Code perspective

Aggregation for Multi-Project Builds

For projects with multiple subprojects, we recommend to aggregate reports before uploading them. This reduces the amount of data that needs to be processed by Teamscale. The plugin works similarly to Gradle's JaCoCo Report Aggregation Plugin and Test Report Aggregation Plugin. Please refer to the Gradle documentation for more details on the concepts behind the aggregation mechanism.

Apply the Plugins

Apply both plugins to the aggregation project:

groovy
plugins {
	id "com.teamscale"
	id 'com.teamscale.aggregation'
}

Register Subprojects for Aggregation

The plugin adds the reportAggregation Gradle dependency configuration. You register subprojects in it the same way you declare library dependencies - Gradle then resolves test outputs from those projects for aggregation. There are two typical scenarios:

  • The aggregation project is a JVM project itself - i.e., the aggregation happens in the project that also applies the application plugin. In this case the reportAggregation configuration will automatically depend on all transitive projects and hence aggregate their reports.
  • The aggregation project is a standalone project - i.e., the project contains no code and has no dependencies to other projects declared. This is usually the case if aggregation happens via the root project or a dedicated subproject that just exists for the purpose to aggregate data. In this case you have to ensure that the projects that you want to aggregate are added as dependencies to the reportAggregation configuration.
groovy
plugins {
	id 'com.teamscale.aggregation'
}

dependencies {
	// either register each project from which data should be aggregated separately:
	reportAggregation(project(":lib:lib1"))
	reportAggregation(project(":lib:lib2"))
	// alternatively, if you want to aggregate data from all subprojects:
	reportAggregation(subprojects())
}

Register Upload Tasks

With JVM Test Suites

When using the JVM Test Suite Plugin, aggregate reports and their tasks are automatically created for each test suite - you only need to register a TeamscaleUpload task that collects from them. See the Gradle Plugin Reference for the full list of auto-created task types.

groovy
plugins {
	id 'com.teamscale.aggregation'
}

tasks.register('teamscaleIntegrationTestReportUpload', TeamscaleUpload) {
	partition = 'Integration Tests'
	from(tasks.named('integrationTestAggregateCompactCoverageReport'))
	from(tasks.named('integrationTestAggregateJUnitReport'))
}

Without JVM Test Suites

If your build differentiates different test types only by package/class name rather than using the JVM Test Suite Plugin, no aggregate tasks are created automatically. Use the following approach to explicitly register each test type for separate aggregation. You will need one Test task for each type of test that you want to differentiate in the Teamscale UI.

You can associate a Test task with an arbitrary test suite name. For this purpose we provide the TestSuiteCompatibilityUtil.exposeTestForAggregation(testTask, suiteName). Call this for each task in each subproject that you want to take part in aggregation.

groovy
import com.teamscale.aggregation.TestSuiteCompatibilityUtil

tasks.register('unitTest', Test) {
	// ...
}

tasks.register('systemTest', Test) {
	teamscale {
		collectTestwiseCoverage = true
	}
}

TestSuiteCompatibilityUtil.exposeTestForAggregation(tasks.named('unitTest'), 'myUnitTestSuite')
TestSuiteCompatibilityUtil.exposeTestForAggregation(tasks.named('systemTest'), 'mySystemTestSuite')

In the aggregation project, create an aggregation report for each type of data you want to aggregate (coverage, test results, testwise coverage) and each test suite name you picked:

groovy
import com.teamscale.aggregation.compact.AggregateCompactCoverageReport
import com.teamscale.aggregation.junit.AggregateJUnitReport
import com.teamscale.aggregation.testwise.AggregateTestwiseCoverageReport

plugins {
	id 'com.teamscale.aggregation'
}

reporting {
	reports {
		unitTestAggregateCompactCoverageReport(AggregateCompactCoverageReport) {
			testSuiteName = 'myUnitTestSuite'
		}
		unitTestAggregateJUnitReport(AggregateJUnitReport) {
			testSuiteName = 'myUnitTestSuite'
		}
		systemTestAggregateTestwiseCoverageReport(AggregateTestwiseCoverageReport) {
			testSuiteName = 'mySystemTestSuite'
		}
	}
}

tasks.register("teamscaleUnitTestReportUpload", TeamscaleUpload) {
	partition = "Unit Tests"
	from(tasks.named('unitTestAggregateCompactCoverageReport'))
	from(tasks.named('unitTestAggregateJUnitReport'))
}

tasks.register("teamscaleSystemTestReportUpload", TeamscaleUpload) {
	partition = "System Tests"
	from(tasks.named('systemTestAggregateTestwiseCoverageReport'))
}

Troubleshooting

Log Files

When debugLogging is enabled in the teamscale extension on a Test task, the plugin creates detailed log files to help diagnose issues:

  • logs/teamscale-jacoco-agent.log - Debug logs from the Java profiler
  • engine.log - Debug logs from the impacted test engine
groovy
tasks.register('unitTest', Test) {
	teamscale {
		collectTestwiseCoverage = true
		runImpacted = true
		debugLogging = true
	}
}

These log files will be located in the test task's JaCoCo destination directory (e.g., build/jacoco/unitTest/).

Spring Boot: Missing Dependencies

Spring boot uses its own dependency versioning mechanism, not the Gradle built-in one. This can lead to problems when adding the com.teamscale.aggregation plugin. You'll see error messages like this one:

Could not find org.springframework.boot:spring-boot-starter-validation:.
     Required by:
         root project 'myproject' > project :subproject

The aggregateReportResults configuration is where the plugin resolves reports from subprojects. Setting transitive = false prevents Gradle from following Spring Boot's BOM transitive dependencies into the aggregation resolution, which causes the version conflict.

In the project with the aggregation plugin, add:

dependencies {
  reportAggregation(subprojects)
}

configurations.aggregateReportResults {
    transitive = false
}