Jenkins × Bitbucket × JaCoCo で週次カバレッジレポートを自動化する手順

# はじめに
この記事では、**JenkinsとBitbucketを連携し、JaCoCoでJavaプロジェクトのカバレッジレポートを週次で自動生成・保存・通知する手順**を記載します。

## ✅ 目的

- Jenkinsで **週1回の定期実行**
- テストとともに **コードカバレッジを測定**
- **レポートを保存・Jenkins上で確認可能に**
- （オプションでSlackやメールで通知）

---

## 🛠 前提条件

- JenkinsがBitbucketと連携済み
- ジョブはPipeline（Jenkinsfile）で作成
- Java + Gradle プロジェクト（Mavenは別途対応可能）

---

## ✅ JaCoCoの導入手順（Gradle）

### 1. `build.gradle` に JaCoCo プラグインを追加

```groovy
plugins {
    id 'java'
    id 'jacoco'
}
```

### 2. カバレッジレポート出力設定

```groovy
jacocoTestReport {
    dependsOn test
    reports {
        html.required = true
        xml.required = true
        csv.required = false
    }
}
```

### 3. カバレッジ閾値チェック（任意）

```groovy
jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = 0.80
            }
        }
    }
}
check.dependsOn jacocoTestCoverageVerification
```

---

## ✅ Jenkinsfile の設定（Pipeline）

```groovy
pipeline {
  agent any
  triggers {
    cron('H H * * 1') // 毎週月曜実行
  }
  stages {
    stage('Checkout') {
      steps {
        checkout scm
      }
    }
    stage('Run Tests + Coverage') {
      steps {
        sh './gradlew clean test jacocoTestReport'
      }
    }
    stage('Archive Coverage') {
      steps {
        archiveArtifacts artifacts: 'build/reports/jacoco/test/html/**', fingerprint: true
      }
    }
    stage('Publish HTML Report') {
      steps {
        publishHTML(target: [
          reportName : 'JaCoCo Report',
          reportDir  : 'build/reports/jacoco/test/html',
          reportFiles: 'index.html',
          alwaysLinkToLastBuild: true,
          keepAll    : true
        ])
      }
    }
  }
  post {
    always {
      echo "Build finished. Check JaCoCo report at ${env.BUILD_URL}"
    }
  }
}
```

---

## ✅ Jenkinsプラグイン

以下のプラグインをJenkinsにインストールしてください：

- `HTML Publisher Plugin`
- `Pipeline Plugin`

---

## ✅ 実行確認（ローカル）

```bash
./gradlew clean test jacocoTestReport
```

出力先（HTMLレポート）:

```
build/reports/jacoco/test/html/index.html
```

## ✅ まとめ

| 項目         | 内容                              |
|--------------|-----------------------------------|
| カバレッジ測定 | JaCoCo                            |
| 実行タイミング | Jenkinsのcronによる週次実行       |
| レポート形式 | HTML（閲覧用）/ XML（自動処理用） |
| Jenkins表示   | `publishHTML`によるレポート表示    |

---

以上で週次の自動カバレッジ測定とレポート表示の仕組みが構築できます。

## 追記：マルチプロジェクト構成の場合
### 前提
```
root
├ build.gradle
├ setting.gradle
├ hoge
|   └   build.gradle
├ fuga
|   └   build.gradle
```

### サブプロジェクト（例：hoge/build.gradle）
```groovy
plugins {
    id 'java'
    id 'jacoco'
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
}

test {
    useJUnitPlatform()
    finalizedBy jacocoTestReport
}

jacoco {
    toolVersion = "0.8.11"
}

jacocoTestReport {
    dependsOn test
    reports {
        html.required = true
        xml.required = true
    }
}

jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                minimum = 0.80
            }
        }
    }
}

check.dependsOn jacocoTestCoverageVerification

```

### ルートプロジェクトで複数モジュールの集約レポートを出す
root/build.gradle に追加することで、全体のJaCoCoレポートを生成できる
```groovy
subprojects {
    apply plugin: 'java'
    apply plugin: 'jacoco'

    jacoco{
      toolVersion = "0.8.11"
    }
}

task jacocoRootReport(type: JacocoReport) {
    group = "Verification"
    description = "Generates an aggregate Jacoco coverage report"

    dependsOn subprojects.test

    classDirectories.from = files(subprojects.collect {
        file("${it.buildDir}/classes/java/main")
    }).filter { it.exists() }

    sourceDirectories.from = files(subprojects.collect {
        file("${it.projectDir}/src/main/java")
    }).filter { it.exists() }

    executionData.from = files(subprojects.collect {
        fileTree(dir: it.buildDir, includes: ['jacoco/*.exec'])
    }).filter { it.exists() && it.length() > 0 }

    reports {
        html.required = true
        xml.required = true
    }
}


```

#### カバレッジ対象を絞りたい場合
```
classDirectories.from = files(subprojects.collect {
    file("${it.buildDir}/classes/java/main")
}).asFileTree.matching {
    include 'jp/co/hogehoge/app/controller/**'
    include 'jp/co/hogehoge/domain/service/**'
}
```