【CircleCI】GitHubのバッジにJacocoで取得したカバレッジを表示する【Codecov】

# やること
`GitHub`の`README`に以下のようなカバレッジ表示を出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/d96a03b2-e370-f748-0078-e2bc602338bb.png)

## プロジェクト構成
今回は自作の`OSS`、`KRowMapper`のリポジトリで作業をしました。
このプロジェクトは以下のような構成となっています

- `Kotlin JVM`の`Gradle`プロジェクト
  - `build.gradle`は`Kotlin DSL`で記述
  - テストフレームワークは`JUnit5`
  - カバレッジ計測には`Jacoco`を使用
- `CI`は`CircleCI`で実行
- カバレッジの分析周りは`Codecov`を利用

コードは`GitHub`に公開しています。

- [ProjectMapK/KRowMapper: Spring JDBC RowMapper for Kotlin\.](https://github.com/ProjectMapK/KRowMapper)

# 外部設定
まずプロジェクト外でカバレッジ計測の準備をします。

## Codecovの設定
`GitHub`等のアカウントを利用して`Codecov`にログインし、計測対象にしたいリポジトリを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/731ea659-81d3-f2c2-aa88-9b1e645752f1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/0c7e313c-d66b-5db2-0044-953d7e70b472.png)

選ぶと`token`が生成されます（スクショを撮り忘れたので生成した時の表示の画像が有りません、申し訳ありません）。
この`token`は「`Settings` -> `Repository Upload Token`」から確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/256989da-415c-e4d0-9491-aaee85fd92bd.png)

## CircleCIの設定
次に、`CircleCI`で「`PROJECT SETTINGS` -> `Environment Variables` -> `Add Variable`」から環境変数を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/908ad433-0d2c-649b-3ab1-a065765e2745.png)

`Name`は`CODECOV_TOKEN`で、先ほど生成した`Codecov`の`token`を入力します。
この時`token`はダブルクォートで囲まずそのままで入力する必要が有ります（[ダブルクォートで囲ってしまった場合カバレッジが上手く反映されません](https://qiita.com/wrongwrong/items/75f9bd9e569fda3fd67d)）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/0b20d884-eee3-53c2-daac-85416c288fc4.png)

### Orb Security Settingsの設定
`CircleCI`で`Codecov`の`Orb`を利用する場合、`Organization Settings`から`Orb Security Settings`で`Yes`を指定する必要が有ります。
この設定は`Organization`等の単位で設定するため、これまでに何も触っていない場合は設定が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/ac96d0e9-47ba-e3e5-2f17-7292c5de92a4.png)

この設定無しで実行した場合、`To use this orb, an organization admin must opt-in to using third party orbs in Organization Security settings.`となって失敗します。

<details><summary>失敗した様子</summary><div>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/23797ae9-9215-ba7b-8206-aebf10c56186.png)
</div></details>

# 内部設定
続いてリポジトリ内での設定をします。

## Jacocoによるレポートの出力設定
`build.gradle`に`Jacoco`によるレポート出力を追記していきます。

まず`plugins`に`jacoco`を追加します。

```kotlin:build.gradle.kts
/* 略 */

plugins {

    /* 略 */

    id("jacoco")
}

/* 略 */
```

続いて`tasks`に`jacocoTestReport`を追加します。
この設定は[こちらの記事](https://blog.masuqat.net/2016/03/21/circleci-and-codecov-for-kotlin/)を参考にしています。

```kotlin:build.gradle.kts
/* 略 */

tasks {

    /* 略 */

    test {
        useJUnitPlatform()
    }

    jacocoTestReport {
        reports {
            xml.isEnabled = true
            csv.isEnabled = false
            html.isEnabled = true
        }
    }
}
```

最後に、自分はテスト実行後に自動でカバレッジのレポートを生成する設定を行いました。

```kotlin:build.gradle.kts
tasks {

    /* 略 */

    test {
        useJUnitPlatform()
        // テスト終了時にjacocoのレポートを生成する
        finalizedBy(jacocoTestReport)
    }

    jacocoTestReport {
        reports {
            xml.isEnabled = true
            csv.isEnabled = false
            html.isEnabled = true
        }
    }
}
```

最終的に`build.gradle.kts`は以下のようになりました（実際に利用している内容なため、今回の設定に対しては余計な内容が多く含まれています）。

<details><summary>`build.gradle.kts`</summary><div>

```kotlin:build.gradle.kts
plugins {
    id("maven")
    id("java")
    id("org.jetbrains.kotlin.jvm") version "1.3.72"
    id("org.jlleitschuh.gradle.ktlint") version "9.2.1"
    id("jacoco")
}

group = "com.mapk"
version = "0.14"

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
}

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath(kotlin("gradle-plugin"))
    }
}

repositories {
    mavenCentral()
    maven { setUrl("https://jitpack.io") }
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation(kotlin("reflect"))
    api("com.github.ProjectMapK:Shared:0.16")
    // 使うのはRowMapperのみなため他はexclude、またバージョンそのものは使う相手に合わせるためcompileOnly
    compileOnly(group = "org.springframework", name = "spring-jdbc", version = "5.2.4.RELEASE") {
        exclude(module = "spring-beans")
        exclude(module = "spring-jcl")
        exclude(module = "spring-tx")
    }

    // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
    testImplementation(group = "org.junit.jupiter", name = "junit-jupiter", version = "5.6.2") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    }
    // https://mvnrepository.com/artifact/io.mockk/mockk
    testImplementation("io.mockk:mockk:1.10.0")

    // テスト時には無いと困るため、別口でimplementation
    testImplementation(group = "org.springframework", name = "spring-jdbc", version = "5.2.4.RELEASE")
    // https://mvnrepository.com/artifact/com.h2database/h2
    testImplementation(group = "com.h2database", name = "h2", version = "1.4.200")

    // 現状プロパティ名の変換はテストでしか使っていないのでtestImplementation
    // https://mvnrepository.com/artifact/com.google.guava/guava
    testImplementation(group = "com.google.guava", name = "guava", version = "28.2-jre")
}

tasks {
    compileKotlin {
        dependsOn("ktlintFormat")
        kotlinOptions {
            jvmTarget = "1.8"
            allWarningsAsErrors = true
        }
    }

    compileTestKotlin {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict")
            jvmTarget = "1.8"
        }
    }
    test {
        useJUnitPlatform()
        // テスト終了時にjacocoのレポートを生成する
        finalizedBy(jacocoTestReport)
    }

    jacocoTestReport {
        reports {
            xml.isEnabled = true
            csv.isEnabled = false
            html.isEnabled = true
        }
    }
}

```
</div></details>

## .circleci/config.ymlにCodecov関連を設定する
まず[クイックスタート](https://circleci.com/orbs/registry/orb/codecov/codecov)を参考に`Codecov`の`Orb`を追加します。
自分のプロジェクトでは`config`のバージョンが`2.0`だったため、これを機に`2.1`に上げました。

```yml:config.yml
version: 2.1
orbs:
  codecov: codecov/codecov@1.0.5

# 略

```

続いて、`CI`の最後に`Codecov`にカバレッジ関連ファイルを上げる設定を追加します。

```yml:config.yml
# 先ほど設定した部分
version: 2.1
orbs:
  codecov: codecov/codecov@1.0.5

# 略

jobs:
  build:

    # 略

    steps:

      # 略

      # run tests!
      - run: gradle test
      # ↓2行を追加
      # upload coverages to codecov 
      - run: bash <(curl -s https://codecov.io/bash)
```

最終的に`.circleci/config.yml`は以下のようになりました。
内容はほぼ[Language Guide: Java](https://circleci.com/docs/2.0/language-java/)のコピペです。

<details><summary>`.circleci/config.yml`</summary><div>

```yml:config.yml
# Java Gradle CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2.1
orbs:
  codecov: codecov/codecov@1.0.5
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/openjdk:8-jdk

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    environment:
      # Customize the JVM maximum heap limit
      JVM_OPTS: -Xmx3200m
      TERM: dumb

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "build.gradle.kts" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: gradle dependencies

      - save_cache:
          paths:
            - ~/.gradle
          key: v1-dependencies-{{ checksum "build.gradle.kts" }}

      # run lints!
      - run: gradle ktlintCheck
      # run tests!
      - run: gradle test
      # upload coverages to codecov
      - run: bash <(curl -s https://codecov.io/bash)

```
</div></details>

# GitHubで動作を確認する
最後に、これまでの設定から`GitHub`での動作を確認します。

## Pull Requestを作成する
`PR`を出した際に`CI`を回す設定になっていれば、以下のように`Codecov Report`が届きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/98b02481-7df9-db3d-d3ad-fb78fa5d6563.png)

実際に`Codecov Report`が届いているPRです。

- [Add display coverage\. by k163377 · Pull Request \#23 · ProjectMapK/KRowMapper](https://github.com/ProjectMapK/KRowMapper/pull/23)

## READMEにバッジを貼る
`Codecov`の「`Settings` -> `Badge`」から各フォーマットでのバッジが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/49ff86aa-0c06-30c5-0743-c74c7dda8e7a.png)

これを`README`に貼ることで、以下のようなカバレッジ表示を出せます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/d96a03b2-e370-f748-0078-e2bc602338bb.png)

# 参考にさせて頂いた記事
- [CircleCIとCodecovでGitHubにカバレッジバッジをつけよう！ \- VELTRA Engineering \- Medium](https://medium.com/veltra-engineering/how-to-add-coverage-badge-to-github-repository-using-circleci-and-codecov-663dd412d95f)
- [Kotlin で CircleCI と Codecovを使う（ JUnit \+ kotlin\-test \+ knit ） \| La Verda Luno](https://blog.masuqat.net/2016/03/21/circleci-and-codecov-for-kotlin/)
- [GradleでTestタスク実施と同時にJacocoでレポートを出力する \- Qiita](https://qiita.com/oono_yoshi/items/a0aa2fff171683006dbb)
- [CircleCI Orb Registry \- codecov/codecov](https://circleci.com/orbs/registry/orb/codecov/codecov)