DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

コードメトリクス収集ツールoctocovの紹介とLooker Studioによる可視化応用例


こんにちは、SWETの飯島です。

本日はコードメトリクスを管理/収集するツールoctocovの紹介と、Googleのデータ可視化ツールLooker Studioを用いたメトリクス可視化の応用例を紹介します。

なお、これから紹介するoctocovやLooker Studioの例は任意の言語で応用できますが、本記事ではC#のプロジェクトを対象に解説をします。

octocov

ファイル単位のテストカバレッジを見たい

octocovの話へ移る前に、テストカバレッジ1の管理がなぜ必要なのかを述べなくてはなりません。

テストカバレッジが管理されていないと、テストカバレッジの減少、すなわちテストの欠落に気付けなくなってしまいます。 テストの欠落は、何らかの変更を加えた後に既存の機能が意図したように動作しない現象(リグレッション)や、リファクタリング時に元の機能を壊してしまうことなどを招きかねません。

テストが欠落する問題に対して、コードのレビュー時にファイル単位のカバレッジをレビュー項目に追加するという対策が考えられます。 たとえばコードの作成者がテストを書き忘れたとしても、カバレッジが不自然に低いファイルをレビュー者がチェックできれば、テストの抜け漏れを防げるようになります。 また、レビューにカバレッジを用いることでテストへの意識がチーム内で共有され、カバレッジが高いテストを個々人が記述するようになるという副次的な効果も期待できます。

コードレビュー時にカバレッジを表示するツール

コードレビュー時にカバレッジを表示できるツールは複数リリースされています。

今回は外部にデータを送信しない点と無料で使用できる点を重視し、octocovを採用しました。

octocov

octocovはコードメトリクスを収集して管理するオープンソースツールです。 octocovが扱うコードメトリクスには、テストの実行時間やコードとテストの比率、そしてリポジトリ全体・ファイル毎のテストカバレッジも含まれています。

また、コードのレビュー時にファイル単位のカバレッジを表示させることもoctocovなら簡単に自動化できます。 PRの更新をトリガーに、テストと合わせてoctocovを実行させると、octocovはPRのコメントとしてテストカバレッジ等の各種メトリクスを投稿します。

octocovがPRにコメントしている様子

octocovを導入することで、上の画像のようにコメントされます。

octocovの実行例

octocovがPRのコメントとしてコードのメトリクスを投稿するまでの流れをC#のプロジェクトで下に再現します。

name: test

on:
  workflow_dispatch:
  pull_request: # PRのコメントとして投稿するなら、トリガーにPRを含めましょう

jobs:
  test:
    permissions: # (1) octocovの機能をフルで使うには適切な権限を与える必要があります
      contents: 'read'
      id-token: 'write'
      actions: 'read'
      pull-requests: 'read'
    env:
      DOTNET_VERSION: '8.0.x'
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Setup .NET Core SDK ${{ env.DOTNET_VERSION }}
        uses: actions/setup-dotnet@v2
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      - name: Install dependencies
        run: |
          dotnet restore MyProject.sln
          dotnet tool restore

      # (2) テストの際にメトリクス計測の形式を指定することで、テストカバレッジの収集が有効になります
      - name: Run dotnet test with measuring coverage
        run: dotnet test ./MyProject.Tests/MyProject.Tests.csproj --collect:"XPlat Code Coverage" --results-directory "TestResults" --no-restore

      # (3) 1つ上のステップで作成したレポートを1つにまとめます
      - name: Merge Coverage
        run: dotnet dotnet-coverage merge -o output.cobertura.xml -f cobertura "TestResults/**/coverage.cobertura.xml"

      # (4) octocovには専用のアクションが用意されているので、これを実行します
      - name: Run octocov
        uses: k1LoW/octocov-action@v1
        with:
          config: .octocov.yml
          install-dir: ${{ runner.temp }}/
          work-dir: ${{ github.workspace }}
          github-token: ${{ github.token }}
        env:
          GITHUB_ENTERPRISE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.MYPROJECT_GCP_CREDENTIALS }}

実行にあたり、いくつかの注意点があります。

1. jobs.<job_id>.permissions

適切な権限がトークンに付与されていないと、octocov-actionsの処理の一部がスキップされてしまいます。 たとえば、pull-requests: 'read'が付与されていない場合、octocov-action実行時にテストカバレッジに関する処理がスキップされ、下のように出力されます。

Skip measuring code coverage: the condition in the `if` section is not met (true): GET https://github.com/api/v3/repos/swet/my-project/pulls/100: 403 Resource not accessible by integration []

なお、権限により処理のいくつかがスキップされてもジョブとしては成功するケースもありますので、成功していてもoctocov-actionのログはしっかり確認するようにしましょう。

2. dotnet test

C#ではdotnet test--collect:"XPlat Code Coverage"を加えると、コードメトリクスを収集しつつテストが実行されます。 出力されるメトリクスのレポートはoctocovがサポートしている形式に従う必要があります。 詳細はこちらをご覧ください。

3. dotnet dotnet-coverage merge

こちらもC#特有の処理です。上記の例ではcobertura形式で複数のレポートをoutput.cobertura.xmlに集約しています。

4. octocov-action

octocovはCLIツールだけでなく、octocov-actionという専用のアクションの提供もしています。

さて、前段のステップで出力されたoutput.cobertura.xmlがoctocov-actionのwithenv内に登場していないことに疑問を持った方もいらっしゃるかもしれません。 実は、.octocov.ymlというoctocovの設定ファイルの中にメトリクスレポートへのパスも記述しています。

下に.octocov.ymlの一例を記します。設定の詳細はoctocovのREADMEをご覧ください。

coverage:
  if: true
  paths:
    - output.cobertura.xml

codeToTestRatio:
  code:
    - 'MyProject/**.cs'
  test:
    - 'MyProject.Tests/**.cs'

testExecutionTime:
  if: true

diff:
  datastores:
    - bq://swet/my-project/octocov

comment:
  if: is_pull_request

summary:
  if: true
report:
  if: is_default_branch
  datastores:
    - bq://swet/my-project/octocov

Looker Studio

octocovの力によりファイル単位のカバレッジを閲覧できる状態になりました。 とはいえ見たいコードメトリクスは他にも考えられます。

  • テストカバレッジやテストの実行時間などのコードメトリクスの推移
  • ディレクトリ毎のテストカバレッジ

コードメトリクスの推移

コードメトリクスの推移を見たい理由

octocovを導入してレビュー時にコードメトリクスを参照できても、コードの品質が良い状態を維持できるとは限りません。 レビュー時のメトリクスが少々悪化している程度では、テストが不十分だと判断するのはハードルが高くなります。 そうなれば低品質なコードがレビューに通ってしまい、さらにそれが常態化すると全体的なコードの品質も徐々に落ちてしまいます。

徐々にメトリクスが悪化するケースに対応するには、ある時点のコードメトリクスの計測だけで満足せず、コードメトリクスの推移まで追う必要があります。

コードメトリクスの推移の可視化

コードメトリクスの推移を可視化するには、大きく2つの段階に分けられます。

  • 過去のコードメトリクスをどこかに蓄積する
  • 蓄積されたデータを基にメトリクスを表示する

当然といえば当然ですが、メトリクスの推移では過去のメトリクスも参照します。 したがって、計測したコードメトリクスがどこかに蓄積され、その蓄積したメトリクスを用いて推移を表示する必要があります。

コードメトリクスを蓄積する

データの蓄積と聞くとハードルが高く感じるかもしれませんが、なんとここでもoctocovが活躍します。

ここで、先ほどの.octocov(octocovの設定ファイル)の最後の箇所を再掲します。

report:
  if: is_default_branch
  datastores:
    - bq://swet/my-project/octocov

上のreport.datastoresでは、BigQueryのURIが指定されています。 このURIがあるだけで、octocovはBigQueryのテーブルにコードメトリクスを格納します。 さらに嬉しいことに、octocovがテーブルのスキーマを自動的に設定するためテーブルの事前準備は不要になります。 これでメトリクスの蓄積部分は完了です。

なお、BigQueryのテーブルスキーマの詳細はこのようになっています。

octocovが作成するBigQueryのテーブルスキーマ

(引用元:https://github.com/k1LoW/octocov/blob/main/docs/bq/schema/README.md

コードメトリクスの推移をLooker Studioで表示する方法

まず、メトリクスの推移の表示先としてLooker Studioを選択しました。 選択の理由は下にいくつか並べますが、特に2番が大きかったと感じます。

  1. データ可視化ツールとして有名かつ人気
  2. BigQueryとLooker Studio間のデータの連携が楽
  3. 極めて安価(Looker Studio自体は無料でも使える)

説明の都合上、Looker Studioの使い方は説明しませんが、グラフとグラフ設定の例を画像として下に貼ります。 余談ですがメトリクスの計測対象にはAnjinという弊社が開発したOSSツールを選定しました。

Anjinにおけるテストの実行時間の推移

折れ線で表示しているtest_execution_secondstest_execution_timeを109で割った値として独自に定義した指標になります。

以上でコードメトリクスの推移を表示できるようになりました。

ディレクトリ毎のカバレッジ

ディレクトリ毎のカバレッジを見たい理由

octocovの導入だけでもファイル毎のカバレッジを閲覧できますが、ディクレトリ毎のカバレッジを見たいケースもあるかと思います。 たとえば、レビューするファイル数が多い場合や非レビュー時のようなケースでは、扱うファイル数が多くなりがちで、ファイル毎のカバレッジから有意義な考察を得られ難くなってしまいます。 カバレッジをディレクトリ毎にまとめることで、表示数が抑えられ、ディレクトリの傾向を把握しやすくなるというメリットを得られます。

ディレクトリ毎のカバレッジをBigQueryに用意する

ディレクトリ毎のカバレッジについて、octocovは自動的には計測してくれません。 そこで、octocovがBigQueryに保存したファイル毎のカバレッジから算出します。 どこにファイル毎のカバレッジが保存されているのか疑問に思う方もいるかもしれませんが、実は既に登場しています。

BigQueryのrawカラム

rawカラムには各ファイルのカバレッジがJSON形式で保存されています。 そこで、再帰SQLを用いてファイル毎のカバレッジをディレクトリ毎のカバレッジに変換します。

WITH RECURSIVE latest_record AS (
  SELECT
    raw
  FROM
    `swet.my-project.octocov`
  WHERE coverage_total <> 0
  ORDER BY
    timestamp DESC
  LIMIT 1
),
parsed_json AS (
  SELECT
    JSON_QUERY_ARRAY(raw, '$.coverage.files') AS files_array
  FROM
    latest_record
),
flattened_files AS (
  SELECT
    JSON_EXTRACT_SCALAR(file, '$.file') AS file_path,
    CAST(JSON_EXTRACT_SCALAR(file, '$.total') AS INT64) AS total,
    CAST(JSON_EXTRACT_SCALAR(file, '$.covered') AS INT64) AS covered
  FROM
    parsed_json,
    UNNEST(files_array) AS file
),
directory_paths AS (
  -- 初期状態でのファイルパスとその階層
  SELECT
    file_path,
    total,
    covered,
    SUBSTR(file_path, 1, IFNULL(NULLIF(INSTR(file_path, '/'), 0) - 1, LENGTH(file_path))) AS partial_path,
    SUBSTR(file_path, INSTR(file_path, '/') + 1) AS remaining_path,
    1 AS level
  FROM
    flattened_files

  UNION ALL

  -- 再帰的に階層ごとにパスを抽出
  SELECT
    file_path,
    total,
    covered,
    CONCAT(partial_path, '/', SUBSTR(remaining_path, 1, IFNULL(NULLIF(INSTR(remaining_path, '/'), 0) - 1, LENGTH(remaining_path)))) AS partial_path,
    SUBSTR(remaining_path, INSTR(remaining_path, '/') + 1) AS remaining_path,
    level + 1
  FROM
    directory_paths
  WHERE
    INSTR(remaining_path, '/') > 0
)

-- ディレクトリごとにカバレッジを集計
SELECT
  partial_path AS directory,
  SUM(total) AS total_lines,
  SUM(covered) AS covered_lines,
  ROUND(SUM(covered) / SUM(total) * 100, 2) AS coverage_percentage
FROM
  directory_paths
GROUP BY
  partial_path
ORDER BY
  partial_path;

さて、Looker StudioにはBigQueryのデータをSQLで加工して読み込む機能がありますが、残念ながら再帰SQLには対応していません。 そこで、このクエリを1日に1回実行するようスケジュール化し、クエリの結果を別テーブルに出力させます。 この別テーブルに出力されたデータをLooker Studioで表示させてディレクトリ毎のカバレッジを表示します。 成功し色付けも行うと下のようになります。

Anjinのディレクトリ毎のカバレッジ

ディレクトリ毎のカバレッジに関する注意点

今回はディレクトリ毎のカバレッジをLooker Studioで表示しましたが、C#ではreportgeneratorというテスト結果を可視化するツールでも実現できます。 単にディレクトリ毎のカバレッジを表示したいだけならreportgeneratorを使う方がよいと思います。 ただカバレッジ以外のコードメトリクスも表示させたい場合には、1箇所に集約できるLooker Studioにも分があります。

まとめ

コードメトリクスを管理/収集するツールとしてoctocovを紹介しました。 octocovはコードのレビュー時にテストカバレッジ等の各種メトリクスをPRのコメントとして投稿する機能を有しています。 また、octocovとBigQueryの連携機能をフルに使うと、コードメトリクスの推移やディレクトリ毎のカバレッジをLooker Studioで表示できます。

本記事が皆様の参考になれば幸いです。 DeNA Testing Blogではソフトウェアテストやその隣接領域に関する記事や勉強会の登壇資料などを投稿しています。 また、弊社ではDeNA EngineeringDeNA公式Xアカウント@DeNAxTechでも情報の発信をしております。 ブックマーク・フォロー等よろしくお願いします!


  1. 当記事に出現するテストカバレッジはステートメントカバレッジ(C0カバレッジ)を想定しています。