Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 116 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Shared Java annotations and utilities for _Software Engineering Done Right_ proj

[cols="1,1"]
|===
|Latest version |`0.2.1`
|Latest version |`0.2.6`
|Group ID |`com.arc-e-tect.sedr.utils`
|Artifact ID |`sedr-library`
|===
Expand Down Expand Up @@ -141,3 +141,118 @@ In GitHub Actions, `GITHUB_ACTOR` and `GITHUB_TOKEN` are injected automatically
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
----

=== Enforcing the justification convention with ArchUnit

The library ships a `test-fixtures` artifact that contains the reusable ArchUnit rule `AbstractCoverageExclusionConventionsTest`.
It verifies that every use of `@ExcludeFromJacocoGeneratedCodeCoverage` in your production code carries a non-blank `justification`.

==== Add the test-fixtures dependency

.Gradle (Kotlin DSL)
[source,kotlin]
----
dependencies {
testImplementation(testFixtures("com.arc-e-tect.sedr.utils:sedr-library:0.2.6"))
}
----

.Gradle (Groovy DSL)
[source,groovy]
----
dependencies {
testImplementation testFixtures('com.arc-e-tect.sedr.utils:sedr-library:0.2.6')
}
----

The test-fixtures artifact brings `archunit-junit5` and `junit-jupiter-api` in as transitive dependencies, so no extra entries are needed for those.
You do need to add the JUnit Jupiter engine for test execution:

.Gradle (Kotlin DSL)
[source,kotlin]
----
dependencies {
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.4")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
----

.Gradle (Groovy DSL)
[source,groovy]
----
dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.14.4'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
----

==== Write the convention test

Extend `AbstractCoverageExclusionConventionsTest` and override `getBasePackage()` to scope the rule to your project's root package.

[source,java]
----
class CoverageExclusionConventionsTest extends AbstractCoverageExclusionConventionsTest {

@Override
protected String getBasePackage() {
return "com.example.myapp";
}
}
----

The inherited test `coverageExclusionAnnotationsMustHaveJustification()` runs automatically and fails if any class, constructor, or method in the scanned packages carries `@ExcludeFromJacocoGeneratedCodeCoverage` without a non-blank `justification`.

==== Using the annotation correctly

.Compliant — justification provided
[source,java]
----
@ExcludeFromJacocoGeneratedCodeCoverage(
justification = "Spring Boot entry point – not unit-testable")
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
----

.Non-compliant — ArchUnit rule will fail
[source,java]
----
@ExcludeFromJacocoGeneratedCodeCoverage // missing justification
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
----

=== Example project

A self-contained example demonstrating both compliant and non-compliant usage is located at link:examples/sedr-library/jacoco-marker/[`examples/sedr-library/jacoco-marker/`].

[cols="1,2"]
|===
|File |Purpose

|`src/main/java/.../CompliantService.java`
|Uses `@ExcludeFromJacocoGeneratedCodeCoverage` with a justification — passes the rule.

|`src/main/java/.../NonCompliantService.java`
|Uses `@ExcludeFromJacocoGeneratedCodeCoverage` without a justification — the ArchUnit test reports a violation for this class.

|`src/test/java/.../CoverageExclusionConventionsTest.java`
|Extends `AbstractCoverageExclusionConventionsTest` targeting the example package.
|===

To run the example (requires the library to be published to your local Maven repository first):

[source,bash]
----
# 1. Publish the library locally
cd sedr-library && ./gradlew clean build publishToMavenLocal

# 2. Run the example — expected to FAIL with one ArchUnit violation
cd ../examples/sedr-library/jacoco-marker && ./gradlew test
----

The test run reports exactly one violation:
`Method NonCompliantService.stop() uses @ExcludeFromJacocoGeneratedCodeCoverage without a justification`.
`CompliantService` is not mentioned.
41 changes: 41 additions & 0 deletions examples/sedr-library/jacoco-marker/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Example project demonstrating the sedr-library jacoco-marker annotation
* and the reusable ArchUnit convention test shipped as a test-fixtures artifact.
*
* Prerequisites:
* cd ../../../sedr-library && ./gradlew clean build publishToMavenLocal
*
* Run the example (intentionally fails to demonstrate the ArchUnit check):
* ./gradlew test
*/
plugins {
id 'java'
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

repositories {
// mavenLocal() first so the locally published snapshot is found during
// development before the library is released to Maven Central.
// Once released, the same repositories block resolves from Maven Central
// without any change.
mavenLocal()
mavenCentral()
}

def sedrLibraryVersion = '0.2.6'

dependencies {
implementation("com.arc-e-tect.sedr.utils:sedr-library:${sedrLibraryVersion}")
testImplementation(testFixtures("com.arc-e-tect.sedr.utils:sedr-library:${sedrLibraryVersion}"))
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.14.4')
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
}

test {
useJUnitPlatform()
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading