JaCoCo のカバレッジ母数が減る(Lombok 更新後も BUILD SUCCESS)

Lombok を上げると JaCoCo のカバレッジ母数が縮む(率は上がり、閾値は通る)

FIX SUMMARY verified
Applies when
eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ffLombok 1.18.34+ (adds lombok.@Generated to generated members by default) + jacoco-maven-plugin (filters members annotated @Generated since 0.8.2)bumping Lombok past 1.18.32 in a project measured with JaCoCo — generated accessors leave the coverage denominator, so the reported ratio rises and a jacoco:check threshold that was failing starts passing, with no error, no warning and no test added

Verified: reproduced in eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff, then the LOMBOK_GENERATED_COVERAGE_SHRINK signature was gone after the fix (exit 0).

変えたのは Lombok の版だけ。ソースもテストも JaCoCo の版もそのままで、カバレッジレポートの数字が入れ替わります。

集計Lombok 1.18.32Lombok 1.18.46
METHOD(母数 / 被覆)8 / 5 = 62.50%2 / 2 = 100.00%
LINE(母数 / 被覆)5 / 4 = 80.00%2 / 2 = 100.00%
INSTRUCTION(母数 / 被覆)30 / 20 = 66.67%9 / 9 = 100.00%

BUILD SUCCESS は両方とも変わりません。instrumentation の例外も出ない。率は下がるのではなく上がっています。 そのため、カバレッジの閾値チェックは止める側に回りませんでした。同じ設定(jacoco:check・METHOD 80%)での実測です。

lombok 1.18.32:
[WARNING] Rule violated for bundle cov: methods covered ratio is 0.62, but expected minimum is 0.80
[INFO] BUILD FAILURE

lombok 1.18.46:
[INFO] All coverage checks have been met.
[INFO] BUILD SUCCESS

テストは 1 行も足していません。落ちていたゲートが、依存を 1 つ上げたら通ります。

手がかりになるのは、レポートの母数だけです。エラーメッセージも警告も出ないので、jacoco.xml の <counter type="METHOD" missed=".." covered=".."/> か、HTML レポートの Missed Methods の列を、 更新の前後で見比べることになります。

何が変わったのか

Lombok は 1.18.34 から、生成したメンバへ lombok.@Generated を既定で付けます。javap で数えたものです。

javap -v -p dev.errfix.Profile | grep -c lombok/Generated

lombok 1.18.32  →  0
lombok 1.18.34  →  1
lombok 1.18.46  →  1

JaCoCo は、単純名に Generated を含む注釈(保持方針が class か runtime)が付いたクラス・メソッドを レポートの集計から外します。この除外自体は JaCoCo 0.8.2(2018 年)からある動作で、新しくありません。 新しいのは、Lombok がその注釈を既定で付けるようになったことです。

reproduce したプロジェクトに lombok.config は置いていません。設定を誰かが変えたのではなく、既定動作が 動いたことによる変化になります。

境界は Lombok 側の 1.18.32 と 1.18.34 のあいだ。JaCoCo の版を 0.8.11 / 0.8.13 / 0.8.15 と振っても、 同じ Lombok に対する集計は 3 つとも同じ値でした(除外の実装が 0.8.2 から入っているため)。

母数が縮む範囲

消えるのは Lombok が生成したメンバだけで、手で書いたコードは残ります。上の表の 8 → 2 の内訳です。

メンバ由来1.18.321.18.46
既定コンストラクタ手書きクラスの暗黙生成数える数える
describe()手書き数える数える
getName / setName ほか 6 つ@Getter / @Setter数える数えない

@Data を付けたクラスは生成されるメンバが多いので、減り幅も大きくなります。@Data + @NoArgsConstructor + @AllArgsConstructor の DTO 1 つと、手書きのクラス 1 つを置いて測った例です。

集計Lombok 1.18.32Lombok 1.18.46
CLASS(母数)21
METHOD(母数 / 被覆)12 / 4 = 33.33%2 / 2 = 100.00%
LINE(母数 / 被覆)7 / 4 = 57.14%2 / 2 = 100.00%
BRANCH(母数)16集計に現れない

全メンバが生成物であるクラスは、クラス単位でも数えられなくなります(CLASS の母数が 2 から 1 へ)。 jacoco.xml にはその <class> 要素自体が残り、減るのはカウンタへの寄与だけです。@Data が作る equals / hashCode の分岐が丸ごと外れるため、BRANCH のカウンタは 集計そのものから姿を消します。

どちらの挙動を選ぶか

生成コードをカバレッジの対象外にするのは、それ自体としては筋の通った既定です。getName() を呼ぶだけの テストを書いて率を保つ作業には意味がない、という立場になる。この記事が扱うのは「どちらが正しいか」ではなく、 版を上げた時点で挙動が切り替わり、レポートの数字に断絶ができることのほうです。

数字を旧版と揃えたい場合は、lombok.config に次の 1 行を置きます。

lombok.addLombokGeneratedAnnotation = false

Lombok は各ソースファイルのディレクトリから上へたどって lombok.config を探すので、そのソースの祖先 ディレクトリに無いと読まれません。src/main/java を持つ単一モジュールなら、その src と同じ階層 (プロジェクト直下)で読まれます。

マルチモジュールでは、置いた場所だけでは決まりません。 設定はディレクトリとその配下に効き、上の階層へ たどって集まりますが、ソースファイルに近い設定のほうが優先します。子モジュール側に別の lombok.config が あれば、集約プロジェクト直下に置いた値はそのモジュールに届きません。config.stopBubbling = true が途中にあれば、 そこから上は読まれなくなります。この記事が実測したのは単一モジュールだけなので、複数モジュールの構成では 実効値を確かめてください。Lombok 自身がその手段を持っています。

java -jar lombok.jar config <ソースファイル or ディレクトリ>

生成物の側から確かめる手もあります。

javap -v -p -cp target/classes dev.errfix.Profile | grep -c lombok/Generated
→ 0 なら、注釈は付いていない

設定を戻した実測では、METHOD の母数が 8 に、率が 62.50% に戻りました。

新しい既定のまま進めるなら、閾値の再設定を同じコミットで行います。 母数が変われば率の意味も変わります。 更新前の閾値を据え置くと、上の check のように基準が緩んだ状態でビルドが成功し続けます。

切り分け

  • カバレッジが 0% になった / レポートが空:これは母数が縮む話ではなく、計測そのものが動いていない状態です。 prepare-agent の実行が設定されているか、Surefire の argLine を上書きして JaCoCo のエージェント指定を 消していないかを見ます。本記事の境界とは別です。
  • 率が下がった:除外が率をどちらへ動かすかは、外れたメンバの被覆率が全体の率より低いか高いかで決まります。 生成アクセサは呼ばれずに残ることが多いので、実測ではどれも上がる向きでした。下がったなら、外れた生成メンバの ほうがよく被覆されていた構成か、同じ更新に別の変更が混ざっています。
  • テストの実行件数そのものが減っている:カバレッジではなくテストの収集の問題です。 Tests run: 0 のままビルドが成功する が同じ 「成功しているのに減る」型を扱っています。あちらは実行件数、こちらは集計の母数。
  • 同じ Lombok 更新でコンパイルが止まった:症状が別で、原因も別です。JDK 23 以降で注釈処理が既定で 走らなくなった件は Lombok の getter が cannot find symbol になる にまとめてあります。
  • Gradle で同じことが起きているか:lombok.@Generated の付与は Lombok 側なのでビルドツールに依らず、 JaCoCo の除外も同じ実装です。ただし本記事で実測したのは Maven の構成だけです。

検証環境

  • image: eclipse-temurin(digest 固定 sha256:68868d04...)。コンパイルと実行は同じイメージ内の OpenJDK 17(JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64)に固定。Maven 3.9.12、Surefire 3.5.2
  • 再現:同一ソース・同一テストで Lombok 1.18.32 と 1.18.34 の 2 つのプロジェクトをそれぞれ mvn clean test し、 jacoco.xml の report レベルの METHOD 母数を比較。1.18.34 側が小さいことを判定スクリプトが確認して 終了コード 1 とシグネチャを出す(Maven は両方とも成功している。1 を返すのは判定スクリプト)
  • 修正:Lombok は 1.18.34 のまま lombok.config に lombok.addLombokGeneratedAnnotation = false を置き、 母数が 1.18.32 と一致することを確認 → 終了コード 0
  • 冒頭の表と jacoco:check の実測は、一部のアクセサだけをテストが触る別のプロジェクトで採取した (ケースのクラスは全メンバが被覆済みで 100% になり、率の動きが出ないため)。3 フィールドの @Getter / @Setter と手書きの describe() を持ち、テストは describe() と一部のアクセサだけを呼ぶ
  • JaCoCo は 0.8.11 / 0.8.13 / 0.8.15 で比較し、同じ Lombok に対して同値であることを確認
  • lombok.@Generated の有無は javap -v -p の出現数で確認(1.18.32 / 1.18.34 / 1.18.46 と、 設定を戻した 1.18.46)
  • @Data の表は、@Data + @NoArgsConstructor + @AllArgsConstructor の DTO と手書きクラス 1 つを 置いた 3 つ目のプロジェクトで採取(JaCoCo 0.8.15)。<class> 要素が残ることも jacoco.xml で確認している
  • lombok.config の探索規則(配下に効く/上へたどる/近い設定が優先する/config.stopBubbling)は Lombok の Configuration system のドキュメントによる。実測は単一モジュールのみで、マルチモジュールの 優先順位は本記事では確かめていない
  • 本文がコピペ用に指しているのは Maven Central の現行(Lombok 1.18.46、jacoco-maven-plugin 0.8.15)。 ケースが検証した 1.18.32 / 1.18.34 / 0.8.13 は境界を示す点として残している

再現から修正までは errfix の検証ハーネスが機械的に確認しています。JaCoCo が単純名に Generated を含む注釈を 集計から外すことと、その動作が 0.8.2 で入ったこと(0.8.3 で完全一致から部分一致へ広がったこと)は、 JaCoCo の変更履歴で確認しました。

検証(machine-verified)

この修正は eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff のバージョン固定コンテナ内で再現し、修正後に LOMBOK_GENERATED_COVERAGE_SHRINK のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs jvm/lombok-generated-coverage-shrink
● reproduce LOMBOK_GENERATED_COVERAGE_SHRINK present ✓
● apply fix exit 0
● re-run LOMBOK_GENERATED_COVERAGE_SHRINK gone ✓
PASS verified · eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。