0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jest / Vitest のテスト結果を GitHub Actions Step Summary に見やすく出す CLI を作った

0
Posted at

GitHub Actions を使っていると、
「CI のテスト結果が見づらい」と感じることが増えてきた。

例えば:

  • ログを延々スクロールしないと失敗箇所が分からない
  • Jest / Vitest の JSON を毎回 jq で加工している
  • summary に coverage を出したい
  • failed test を PR 上でサッと確認したい

特に複数 repository へ横展開し始めると、

「毎回 CI 側で JSON を加工する」

実装がかなり辛くなってくる。

そこで今回、
Jest / Vitest の JSON を読み取り、

  • GitHub Actions Step Summary 向け Markdown を生成
  • Coverage 表示
  • Failed Test 一覧
  • ローカル preview

などをまとめて行う CLI を作った。

npm package 名は:

@acme/ci-test-summary

CLI コマンドは:

ci-summary

を使う。


GitHub Actions の $GITHUB_STEP_SUMMARY が便利

GitHub Actions には、

$GITHUB_STEP_SUMMARY

という特殊な環境変数がある。

ここへ Markdown を書き込むと、
Actions UI 上に整形済みの Summary を表示できる。

例えば:

  • 総テスト数
  • 成功 / 失敗数
  • Coverage
  • Failed Test

などを Markdown Table 付きで表示できる。

CI ログを掘らなくても、
PR 上で結果を見やすく確認できるのがかなり便利だった。


利用イメージ

GitHub Actions ではこんな感じで使う。

- name: Run tests
  run: npm run test:ci

- name: Write CI summary
  if: always()
  run: |
    ci-summary \
      --format auto \
      --json artifacts/test-results/unit.json \
      --coverage coverage/coverage-summary.json \
      --title "Unit Tests"

実際には:

  • Jest / Vitest JSON を読み取り
  • Markdown Summary を生成
  • $GITHUB_STEP_SUMMARY に追記

している。


Jest / Vitest は JSON 構造が結構違う

最初は単純に、

jq

で JSON を加工しようとしていた。

単一 repository の間は、
それでもそこまで困らなかった。

しかし:

  • repository 数
  • workflow 数
  • test runner 数

が増え始めると、
runner 差分対応が workflow 側へ散らばり始めた。

さらに formatter 側で:

  • failureMessage の形式差異
  • assertion 配列位置
  • duration の持ち方
  • skipped test の扱い

などを吸収し始めると、

「表示責務なのに runner JSON 構造を知っている」

状態になり、
責務分離が崩れ始めた。

特に CI 系ツールは、
後から:

  • runner
  • formatter
  • reporter
  • output format

が増えやすい。

そのため:

runner 差分は parser 層で閉じ込める

設計に切り替えた。


parser 層で差分を吸収する設計にした

そこで今回は、

Jest JSON
    ↓
Jest Parser
    ↓
NormalizedTestResult
    ↓
Formatter

という構造にした。

Vitest も同じ。

Vitest JSON
    ↓
Vitest Parser
    ↓
NormalizedTestResult
    ↓
Formatter

つまり:

runner 差分は parser 層で閉じ込める

方針にした。


共通型へ正規化する

formatter 側では、

  • Jest
  • Vitest

を意識しない。

共通型だけを見る。

例えば:

export interface NormalizedTestResult {
  total: number;
  passed: number;
  failed: number;
  skipped: number;
  runtimeMs: number | null;
  failedAssertions: FailedAssertion[];
}

こうしておくと、

  • formatter
  • GitHub Summary
  • console preview

などを runner 非依存で扱える。

これは後々かなり効いた。


formatter は「表示責務だけ」に集中させる

formatter 側では:

  • どの runner か
  • JSON 構造がどうか

は一切知らない。

受け取るのは:

NormalizedTestResult

のみ。

結果として:

  • parser
  • formatter
  • coverage
  • CLI

を疎結合にできた。

特に CI 系ツールは、
「責務を混ぜない」ことがかなり重要だと思っている。


--format auto で runner を自動判定

CLI では:

--format auto

を指定できる。

内部では JSON 構造を見て:

  • Jest
  • Vitest

を自動判定している。

例えば:

testResults が存在 → Jest
numTotalTestSuites が存在 → Jest

のような特徴を見る。

利用側としては:

「runner ごとの差を意識しなくていい」

のでかなり楽になった。


Coverage Summary も Markdown 化する

さらに:

coverage-summary.json

も読み取り可能にした。

例えば:

Type Coverage
Lines 91%
Functions 88%
Branches 84%

のような表を Step Summary へ出せる。

CI ログへ coverage 数値を埋め込むより、
Summary の方が圧倒的に見やすい。


Failed Test は <details> で折りたたむ

失敗テストを全部展開すると、
Summary が巨大化する。

そこで:

<details>
  <summary>Failed Tests</summary>
</details>

を使って折りたたむようにした。

さらに:

  • 最大20件まで
  • failure message を整形
  • path を短縮

などを行っている。

CI Summary は、
「全部出す」より:

「必要な情報を短く出す」

方が重要だった。


ローカル実行時は console preview を出す

GitHub Actions 以外では、

$GITHUB_STEP_SUMMARY

が存在しない。

その場合は:

if (process.env['GITHUB_STEP_SUMMARY']) {
  fs.appendFileSync(process.env['GITHUB_STEP_SUMMARY'], `${markdown}\n`);
} else {
  console.log(markdown);
}

として、
標準出力へ Markdown preview を出すようにした。

これによって:

  • ローカル確認
  • formatter デバッグ
  • snapshot 更新

などがやりやすくなった。


CLI としても API としても使えるようにした

今回のツールは:

  • CLI
  • TypeScript API

両方で使えるようにしている。

例えば将来的には:

  • custom formatter
  • Slack 通知
  • PR comment
  • 独自 reporter

などへも流用しやすい。

CI ツールは、
「CLIだけ」に閉じると拡張性が低くなりやすい。


fixture ベースで parser をテストした

JSON parser 系は、
fixture ベースがかなり相性良かった。

例えば:

__tests__/fixtures/

へ:

  • Jest JSON
  • Vitest JSON
  • coverage-summary.json

を置き、

__tests__/parsers/

で parser の結果を検証している。

特に:

  • runner version 差異
  • optional field
  • failure shape

などを固定しやすい。

CLI ツールほど、
fixture test が重要だと感じた。


今後やりたいこと

今後は:

  • GitHub annotations 対応
  • flaky test 集計
  • trend 可視化
  • junit xml support
  • custom formatter plugin

などもやってみたい。

特に:

「CI をただのログ置き場ではなく、観測しやすい品質基盤にする」

方向へ伸ばしていきたいと思っている。

CI は、
単なるログ置き場ではなく、

「品質状態を短時間で観測できる場所」

であるべきだと思っている。

特に repository 数や workflow 数が増え始めると、

  • jq を workflow ごとに書く
  • formatter 実装が散らばる
  • runner 差分対応が各所へ漏れる

状態はかなり辛くなる。

今回の CLI は、
そういった CI 可観測性の負債を減らすために作った。

今後も:

  • flaky test
  • trend 可視化
  • annotations
  • test analytics

などを含め、

「CI を品質基盤として扱う」

方向へ伸ばしていきたい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?