GitHub ActionsでSonarCloudを利用する際にカバレッジを出力する

## はじめに

以下の記事の続きで、GitHubのdotnetプロジェクトにSonarCloudを導入した後にカバレッジも出力できるとのことでやってみました。

https://qiita.com/yusuke-sasaki/items/4016eb74fde053d60559

## 作成したymlファイル

下に[解説](https://qiita.com/yusuke-sasaki/items/51b47b07690f76bad8e9#%E8%A7%A3%E8%AA%AC)があります。
※${{ env.SolutionPath }}のところはわざわざ環境変数を使わずに直接パスを記載でいいと思います。
（一部プロジェクトの状況によりメンテナンスする箇所が出てきてしまったので直しやすいようにしたかったのですが、うまいこといかずslnのパスを環境変数に置いてあるだけです。）

```yml:SonarCould.yml

name: SonarCloud

# ワークフローが動作する条件を指定する
on:
  push:
  workflow_dispatch:

# 仮想環境で行わせる動作を記述する
jobs:
  sonarcloud:
    runs-on: ubuntu-latest
 
    # 環境変数
    env:
      # slnファイルの存在するフォルダの相対パス
      SolutionPath: src

    # リポジトリの内容をチェックアウトする
    # エラーになるためfetch-depth に 0 を指定し全タグ・全ブランチ・全履歴を取得するように設定
    steps:
    - name: Checkout
      uses: actions/checkout@v2
      with:
        fetch-depth: 0

    # dotnet CLIを利用するためにdotNet環境を入れる
    # dotnet-versionにはSDKバージョンを指定する
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.x

    # SonarScannerをインストールする
    - name: Install SonarScanner
      run: dotnet tool install --global dotnet-sonarscanner

    # Testプロジェクトに対してcoverlet.msbuildをインストールする
    # dotnet add packageは1回のコマンドで複数プロジェクトへの動作をサポートしていないためTestプロジェクトの数だけコマンドを追記する必要がある
    - name: Package Add coverlet.msbuild
      run: | 
        dotnet add src/SlackNotification.Tests/*.csproj package coverlet.msbuild
        dotnet add src/NugetTest.Tests/*.csproj package coverlet.msbuild

    # 依存関係を復元する
    - name: Restore
      run: dotnet restore ${{ env.SolutionPath }}

    # 解析を開始する
    #   k:SonarCloudで設定したプロジェクトキー
    #   o:SonarCloudで設定した組織
    #   d:sonar.login:SonarCloudへの認証に必要なトークンを指定する（この引数を指定した場合は終了ステップにも追加する必要がある）
    #   d:sonar.host.url:SonarCloudのURL
    #   d:sonar.cs.opencover.reportsPaths:カバレッジレポートへのパスを指定する（SonarCloudに出力するカバレッジレポートを指定）
    - name: Begin Sonar analysis
      run: dotnet sonarscanner begin /k:"yus-sasaki_NugetSample" /o:"yus-sasaki" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

    - name: Build
      run: dotnet build ${{ env.SolutionPath }} --configuration Release --no-restore

    # テスト時にカバレッジレポートを出力するようにオプションを指定する
    #   p:CollectCoverage=true:コードカバレッジを有効化し、カバレッジレポートを出力するようにする
    #   p:CoverletOutputFormat=opencover:出力するカバレッジレポートの種類を指定
    - name: Test
      run: dotnet test ${{ env.SolutionPath }} /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

    # 解析を終了する
    - name: End Sonar analysis
      run: dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
```

### 解説

公式が提供しているdotnetのymlサンプルを結構変えてしまったので解説します。
`- name: Install SonarScanner`あたりまではdotnetのGitHub Actionsのよくある記述なので省略します。

#### ①SonarScannerをインストールする

```yml
    # SonarScannerをインストールする
    - name: Install SonarScanner
      run: dotnet tool install --global dotnet-sonarscanner
```

SonarCloudのスキャンには自動分析とCIベース分析の2つのアプローチがありますが、自動分析は1つのリポジトリに1つのプロジェクトがある想定で動作するため複数プロジェクトに動作させるにはCIベース分析で行う必要があります。
CIベース分析を行うためにはスキャナーをインストールし、`dotnet sonarscanner begin ~ end`コマンドを利用してスキャンを実行およびSonarColudに結果を送信します。
そのため、まずは`sonarscanner`をインストールしています。

#### ②カバレッジレポートを出力するためcoverlet.msbuildをインストールする

```yml
    # Testプロジェクトに対してcoverlet.msbuildをインストールする
    # dotnet add packageは1回のコマンドで複数プロジェクトへの動作をサポートしていないためTestプロジェクトの数だけコマンドを追記する必要がある
    - name: Package Add coverlet.msbuild
      run: | 
        dotnet add src/SlackNotification.Tests/*.csproj package coverlet.msbuild
        dotnet add src/NugetTest.Tests/*.csproj package coverlet.msbuild
```

SonarColudでカバレッジの情報を利用したい場合は、カバレッジレポートをSonarColudに送信する必要があります。
カバレッジレポートを出力するにあたり、[coverlet.msbuild](https://www.nuget.org/packages/coverlet.msbuild)を利用しています。プロジェクトにnugetパッケージを事前にインストールしていても良いですが、なるべくプロジェクトには手を加えたくないため、仮想環境上でのみインストールしています。
ここで注意する点としては`dotnet add package`は単体のプロジェクトに対しての動作しかサポートしておらず、1回のコマンドで複数プロジェクトに対する動作を行うことができません。1つしかない場合は`run: dotnet add **/*Tests.csproj package coverlet.msbuild`のようにして汎用的に書くこともできますが、複数のテストプロジェクトがある場合はその数だけコマンドを追記しなければなりません。

#### ③解析を開始する際にオプションを指定する

```yml
    # 解析を開始する
    #   k:SonarCloudで設定したプロジェクトキー
    #   o:SonarCloudで設定した組織
    #   d:sonar.login:SonarCloudへの認証に必要なトークンを指定する（この引数を指定した場合は終了ステップにも追加する必要がある）
    #   d:sonar.host.url:SonarCloudのURL
    #   d:sonar.cs.opencover.reportsPaths:カバレッジレポートへのパスを指定する（SonarCloudに出力するカバレッジレポートを指定）
    - name: Begin Sonar analysis
      run: dotnet sonarscanner begin /k:"yus-sasaki_NugetSample" /o:"yus-sasaki" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
```
`dotnet sonarscanner begin`でスキャンを開始しますが、その際にいろいろ設定する必要があります。コメントにも示してありますが、SonarCloudとリポジトリを連携する際に設定したプロジェクトキーや組織、トークンといった情報を設定することでスキャン結果をSonarColudに送信できるようになります。
`d:sonar.cs.opencover.reportsPaths`でどのカバレッジレポートを送信するか指定していますが、C#のカバレッジの種類としては`OpenCover`以外にも`dotCover`や`Visual Studio Code Coverage`が利用できるようです。今回は参考にした[記事](https://dev.to/equiman/net-core-unit-test-and-code-coverage-with-visual-studio-code-37bp)が`OpenCover`を利用していたので同じように設定しています。

[カバレッジの種類について公式ドキュメント](https://sonarcloud.io/documentation/analysis/coverage/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062357/6ceb20c8-d583-7e20-6ba0-16f597bc0fcb.png)

#### ④テストを行う際にカバレッジレポートを出力するようにオプションを指定する

```yml
    # テスト時にカバレッジレポートを出力するようにオプションを指定する
    #   p:CollectCoverage=true:コードカバレッジを有効化し、カバレッジレポートを出力するようにする
    #   p:CoverletOutputFormat=opencover:出力するカバレッジレポートの種類を指定
    - name: Test
      run: dotnet test ${{ env.SolutionPath }} /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
```

beginのほうで指定したカバレッジレポートをテスト時に生成するようにオプションを指定してテストを実行します。

- はまったこと  
`dotnet test src --no-build --verbosity normal`のようにテストコマンドにオプションを追加するとなぜだかエラーが出てしまう。オプションを無くせば問題なく動くのだが、原因はよくわからなかった。

```
引数/home/runner/work/NugetSample/NugetSample/src/NugetTest.Tests/bin/Debug/netcoreapp3.1/NugetTest.Tests.dllが無効です。/ helpオプションを使用して、有効な引数のリストを確認してください。
     1>完了ビルドプロジェクト "/home/runner/work/NugetSample/NugetSample/src/SlackNotification.sln"（VSTestターゲット）-失敗しました。
```

#### ⑤解析を終了する

```yml
    # 解析を終了する
    - name: End Sonar analysis
      run: dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
```
 beginの際に、`d:sonar.login`にトークンを指定していた場合は終了ステップにも追加する必要があるとのことで指定する。

## 最後に

ymlファイルにカバレッジを含めるように設定したことで、以下のようにSonarCloudでカバレッジについても確認できるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062357/de845ca4-7908-83a4-86f5-457f22ca106f.png)

## 参考

参考にしたymlファイル

https://github.com/mr-potato-head/test_sonarcloud/blob/master/.github/workflows/build.yml

カバレッジの設定について参考にした記事

https://dev.to/equiman/net-core-unit-test-and-code-coverage-with-visual-studio-code-37bp