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 を品質基盤として扱う」
方向へ伸ばしていきたい。