Jest を可観測化する:JUnit/SARIF/OpenTelemetry で CI ダッシュボードを構築
テストフレームワークの結果を眺めるだけでは、チーム全体のテスト品質やパフォーマンスを俯瞰することはできません。Jest のテスト結果を JUnit、SARIF、OpenTelemetry といった標準フォーマットで出力し、CI ダッシュボードで可視化すれば、テストの成功率や実行時間、セキュリティの脆弱性まで一元管理できるようになります。
本記事では、Jest を可観測化するための具体的な手法を解説していきます。JUnit XML でテスト結果を収集し、SARIF でセキュリティレポートを統合し、OpenTelemetry でパフォーマンスをトレースする方法を、段階的に説明していきましょう。
背景
テスト結果の可観測性とは
可観測性(Observability)とは、システムの内部状態を外部から観測できる度合いを指します。テストにおける可観測性とは、テストの実行結果、パフォーマンス、品質指標を継続的に収集・可視化し、問題の早期発見や改善を可能にすることです。
Jest は標準でコンソールにテスト結果を出力しますが、以下のような情報を構造化された形式で保存・分析することで、より深い洞察を得られます。
mermaidflowchart TB
jest["Jest テスト実行"] -->|"標準出力"| console["コンソール<br/>ログ"]
jest -->|"JUnit XML"| junit["JUnit レポート<br/>(成功/失敗)"]
jest -->|"SARIF"| sarif["SARIF レポート<br/>(脆弱性)"]
jest -->|"OpenTelemetry"| otel["トレース<br/>(パフォーマンス)"]
junit --> dashboard["CI<br/>ダッシュボード"]
sarif --> dashboard
otel --> dashboard
dashboard --> insights["洞察<br/>・傾向分析<br/>・品質向上"]
上記の図が示すように、Jest の実行結果を複数のフォーマットで出力することで、テストの成功率、脆弱性、パフォーマンスを一元的に管理できます。
標準フォーマットの重要性
JUnit XML、SARIF、OpenTelemetry は、いずれも業界標準のフォーマットです。これらを採用することで、以下のメリットがあります。
| # | フォーマット | 用途 | 主な利点 |
|---|---|---|---|
| 1 | JUnit XML | テスト結果の記録 | CI ツール(Jenkins、GitHub Actions など)との統合が容易 |
| 2 | SARIF | 静的解析・脆弱性レポート | セキュリティスキャン結果の標準化、GitHub Security タブとの連携 |
| 3 | OpenTelemetry | 分散トレース・メトリクス | パフォーマンス分析、ボトルネック特定、APM ツールとの統合 |
これらのフォーマットを活用すれば、異なるツールやプラットフォーム間でデータを共有し、一貫性のある可視化が実現できます。
課題
テスト結果の散在
Jest の標準出力は人間には読みやすいのですが、機械的に解析するには不向きです。CI 環境で複数のテストスイートを実行すると、結果が散在し、以下のような課題が生じます。
mermaidflowchart LR
ci["CI パイプライン"] --> test1["テストスイート A"]
ci --> test2["テストスイート B"]
ci --> test3["テストスイート C"]
test1 --> log1["ログ A"]
test2 --> log2["ログ B"]
test3 --> log3["ログ C"]
log1 -.->|"散在"| issue["・結果の統合が困難<br/>・傾向分析ができない<br/>・履歴管理が煩雑"]
log2 -.-> issue
log3 -.-> issue
テスト結果が各ジョブのログに埋もれてしまい、過去の結果との比較や、失敗率の傾向を把握することが難しくなります。
セキュリティレポートの欠如
Jest でテストを実行しても、コード内の脆弱性や静的解析結果を統合的に管理する仕組みがありません。セキュリティスキャンツール(例:ESLint の security プラグイン、npm audit など)の結果を Jest のテストレポートと一緒に管理したい場合、SARIF フォーマットが有効です。
パフォーマンスの可視化不足
テストの実行時間はコンソールに表示されますが、個々のテストケースやセットアップ処理のボトルネックを特定するには不十分です。分散トレーシングを導入すれば、テスト実行フローを詳細に可視化できます。
解決策
JUnit XML レポートでテスト結果を収集
Jest は jest-junit というカスタムレポーターを使って、JUnit XML 形式でテスト結果を出力できます。このレポーターを導入すれば、CI ツールが結果を自動的にパースし、ダッシュボードに表示できるようになります。
導入手順
まず、必要なパッケージをインストールします。
bashyarn add --dev jest-junit
次に、Jest の設定ファイル(jest.config.js または package.json)に jest-junit を追加します。
javascript// jest.config.js
module.exports = {
// 既存の設定...
reporters: [
'default', // 標準のコンソール出力
'jest-junit', // JUnit XML レポーター
],
};
jest-junit の出力先や形式をカスタマイズする場合は、以下のように設定します。
javascript// jest.config.js
module.exports = {
reporters: [
'default',
[
'jest-junit',
{
outputDirectory: './reports', // 出力ディレクトリ
outputName: 'junit.xml', // ファイル名
classNameTemplate: '{classname}', // クラス名のテンプレート
titleTemplate: '{title}', // テストタイトルのテンプレート
ancestorSeparator: ' › ', // 階層の区切り文字
usePathForSuiteName: 'true', // ファイルパスをスイート名に使用
},
],
],
};
これで Jest を実行すると、./reports/junit.xml にテスト結果が出力されます。
bashyarn jest
生成された junit.xml は以下のような構造を持ちます。
xml<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="10" failures="1" errors="0" time="2.345">
<testsuite name="UserService" tests="5" failures="0" errors="0" time="1.234">
<testcase classname="UserService › login" name="should authenticate user" time="0.234"/>
<testcase classname="UserService › logout" name="should clear session" time="0.123"/>
</testsuite>
<testsuite name="OrderService" tests="5" failures="1" errors="0" time="1.111">
<testcase classname="OrderService › createOrder" name="should create new order" time="0.456"/>
<testcase classname="OrderService › cancelOrder" name="should cancel order" time="0.234">
<failure message="Expected order status to be CANCELLED but got PENDING"/>
</testcase>
</testsuite>
</testsuites>
CI ツール(GitHub Actions、Jenkins、GitLab CI など)は、この XML ファイルを読み込んでテスト結果を可視化します。
GitHub Actions での活用例
GitHub Actions では、dorny/test-reporter などのアクションを使って、JUnit XML を自動的にパースできます。
yaml# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: yarn install
- run: yarn jest
# テストレポートを表示
- name: Test Report
uses: dorny/test-reporter@v1
if: always() # テストが失敗しても実行
with:
name: Jest Tests
path: reports/junit.xml
reporter: jest-junit
これにより、プルリクエストのチェック欄にテスト結果が自動的に表示され、失敗したテストケースも一目で確認できます。
SARIF でセキュリティレポートを統合
SARIF(Static Analysis Results Interchange Format)は、静的解析ツールの結果を統一的に記録するための JSON フォーマットです。Jest 自体は SARIF を直接サポートしていませんが、ESLint や npm audit の結果を SARIF に変換し、Jest のテスト結果と一緒に管理できます。
ESLint の結果を SARIF に変換
ESLint は @microsoft/eslint-formatter-sarif を使って SARIF 形式で結果を出力できます。
bashyarn add --dev @microsoft/eslint-formatter-sarif
ESLint を実行し、結果を SARIF ファイルに保存します。
bashyarn eslint . --format @microsoft/eslint-formatter-sarif --output-file reports/eslint.sarif.json
生成された SARIF ファイルは以下のような構造を持ちます。
json{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "ESLint",
"version": "8.50.0"
}
},
"results": [
{
"ruleId": "no-unused-vars",
"level": "warning",
"message": {
"text": "'unusedVar' is defined but never used."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/userService.ts"
},
"region": {
"startLine": 42,
"startColumn": 7
}
}
}
]
}
]
}
]
}
GitHub Security タブとの連携
GitHub Actions で SARIF ファイルをアップロードすると、リポジトリの Security タブに結果が表示されます。
yaml# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: yarn install
- run: yarn eslint . --format @microsoft/eslint-formatter-sarif --output-file reports/eslint.sarif.json
# SARIF ファイルをアップロード
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: reports/eslint.sarif.json
これにより、プルリクエストのチェック欄や Security タブで脆弱性を確認でき、コードレビュー時にセキュリティ観点の指摘も可能になります。
npm audit の結果を SARIF に変換
npm audit の結果を SARIF に変換するには、npm-audit-sarif などのツールを使います。
bashyarn add --dev npm-audit-sarif
以下のスクリプトで npm audit の結果を SARIF 形式で出力します。
bashnpm audit --json | npx npm-audit-sarif > reports/npm-audit.sarif.json
これにより、依存パッケージの脆弱性も SARIF フォーマットで管理でき、GitHub Security タブに統合されます。
OpenTelemetry でパフォーマンスをトレース
OpenTelemetry は、分散トレーシングとメトリクス収集のための標準フレームワークです。Jest のテスト実行をトレースすれば、個々のテストケースの実行時間や、セットアップ処理のボトルネックを可視化できます。
OpenTelemetry の導入
まず、必要なパッケージをインストールします。
bashyarn add --dev @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http
次に、OpenTelemetry の初期化スクリプトを作成します。
javascript// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const {
OTLPTraceExporter,
} = require('@opentelemetry/exporter-trace-otlp-http');
const {
getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');
// トレースエクスポーターの設定(Jaeger、Zipkin、OpenTelemetry Collector などに送信)
const traceExporter = new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces', // OpenTelemetry Collector のエンドポイント
});
// SDK の初期化
const sdk = new NodeSDK({
traceExporter,
instrumentations: [getNodeAutoInstrumentations()], // 自動計装
serviceName: 'jest-tests', // サービス名
});
// SDK を起動
sdk.start();
// プロセス終了時に SDK をシャットダウン
process.on('SIGTERM', () => {
sdk
.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) =>
console.log('Error terminating tracing', error)
)
.finally(() => process.exit(0));
});
Jest を実行する前に、このスクリプトをロードします。
bashnode -r ./tracing.js ./node_modules/.bin/jest
または、package.json のスクリプトに追加します。
json{
"scripts": {
"test:trace": "node -r ./tracing.js ./node_modules/.bin/jest"
}
}
これで Jest のテスト実行がトレースされ、OpenTelemetry Collector に送信されます。
カスタムスパンの追加
個々のテストケースやセットアップ処理を詳細にトレースするには、カスタムスパンを追加します。
javascript// userService.test.ts
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('jest-tests');
describe('UserService', () => {
beforeAll(async () => {
// セットアップ処理をトレース
const span = tracer.startSpan(
'beforeAll: setup database'
);
await setupDatabase();
span.end();
});
test('should authenticate user', async () => {
// テストケースをトレース
const span = tracer.startSpan(
'test: authenticate user'
);
const result = await userService.login(
'user@example.com',
'password'
);
expect(result.success).toBe(true);
span.end();
});
});
これにより、各テストケースの実行時間や依存関係が可視化され、ボトルネックを特定できます。
トレース結果の可視化
OpenTelemetry Collector から送信されたトレースデータは、Jaeger や Zipkin などの可視化ツールで確認できます。
以下は、Docker Compose で Jaeger を起動する例です。
yaml# docker-compose.yml
version: '3'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- '16686:16686' # Jaeger UI
- '4318:4318' # OTLP HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
Jaeger を起動します。
bashdocker-compose up -d
Jest を実行し、トレースデータを送信します。
bashyarn test:trace
ブラウザで http://localhost:16686 にアクセスすると、Jaeger UI でトレース結果を確認できます。各テストケースの実行時間やスパンの依存関係が視覚的に表示され、パフォーマンスのボトルネックを特定できます。
mermaidflowchart LR
jest["Jest 実行"] -->|"トレース"| otel["OpenTelemetry<br/>SDK"]
otel -->|"OTLP HTTP"| collector["OpenTelemetry<br/>Collector"]
collector --> jaeger["Jaeger"]
collector --> zipkin["Zipkin"]
collector --> datadog["Datadog"]
jaeger --> ui["UI で<br/>可視化"]
zipkin --> ui
datadog --> ui
この図が示すように、OpenTelemetry を使えば、複数のバックエンド(Jaeger、Zipkin、Datadog など)にトレースデータを送信でき、チームの好みに応じた可視化ツールを選択できます。
具体例
CI パイプラインでの統合例
ここでは、GitHub Actions を使って、JUnit XML、SARIF、OpenTelemetry を統合した CI パイプラインを構築します。
ワークフロー全体像
mermaidflowchart TB
start["GitHub Actions<br/>開始"] --> checkout["コードを<br/>チェックアウト"]
checkout --> install["依存関係<br/>インストール"]
install --> parallel1["並列処理"]
parallel1 --> lint["ESLint 実行<br/>(SARIF 出力)"]
parallel1 --> audit["npm audit<br/>(SARIF 出力)"]
parallel1 --> test["Jest 実行<br/>(JUnit XML 出力)"]
parallel1 --> trace["Jest トレース<br/>(OpenTelemetry)"]
lint --> upload_sarif["SARIF を<br/>GitHub にアップロード"]
audit --> upload_sarif
test --> upload_junit["JUnit XML を<br/>レポート"]
trace --> upload_trace["トレースを<br/>Jaeger に送信"]
upload_sarif --> dashboard["CI ダッシュボード<br/>で可視化"]
upload_junit --> dashboard
upload_trace --> dashboard
dashboard --> done["完了"]
上記のフローに従って、GitHub Actions のワークフローを作成します。
ワークフローファイル
yaml# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test-and-analyze:
runs-on: ubuntu-latest
services:
# Jaeger をサービスとして起動
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- 16686:16686
- 4318:4318
options: >-
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:16686 || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
次に、ESLint、npm audit、Jest を並列実行します。
yaml# ESLint を実行し、SARIF 形式で出力
- name: Run ESLint
run: |
mkdir -p reports
yarn eslint . --format @microsoft/eslint-formatter-sarif --output-file reports/eslint.sarif.json
continue-on-error: true # エラーがあっても続行
# npm audit を実行し、SARIF 形式で出力
- name: Run npm audit
run: |
npm audit --json | npx npm-audit-sarif > reports/npm-audit.sarif.json
continue-on-error: true
# Jest を実行し、JUnit XML 形式で出力
- name: Run Jest
run: yarn jest --ci --coverage
env:
JEST_JUNIT_OUTPUT_DIR: ./reports
JEST_JUNIT_OUTPUT_NAME: junit.xml
# Jest をトレースモードで実行
- name: Run Jest with Tracing
run: yarn test:trace
env:
OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:4318
OTEL_SERVICE_NAME: jest-tests
最後に、各レポートをアップロードします。
yaml# SARIF ファイルを GitHub Security タブにアップロード
- name: Upload ESLint SARIF
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: reports/eslint.sarif.json
category: eslint
- name: Upload npm audit SARIF
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: reports/npm-audit.sarif.json
category: npm-audit
# JUnit XML レポートを表示
- name: Test Report
uses: dorny/test-reporter@v1
if: always()
with:
name: Jest Tests
path: reports/junit.xml
reporter: jest-junit
# カバレッジレポートをアップロード
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
flags: unittests
name: codecov-umbrella
このワークフローを実行すると、以下の結果が得られます。
| # | 項目 | 出力先 | 確認方法 |
|---|---|---|---|
| 1 | ESLint の静的解析結果 | GitHub Security タブ | リポジトリ → Security → Code scanning alerts |
| 2 | npm audit の脆弱性レポート | GitHub Security タブ | リポジトリ → Security → Code scanning alerts |
| 3 | Jest のテスト結果 | プルリクエストのチェック欄 | プルリクエスト → Checks → Jest Tests |
| 4 | Jest のカバレッジ | Codecov | Codecov のダッシュボード |
| 5 | Jest のトレース | Jaeger UI | http://localhost:16686 (ローカル環境) |
ローカル環境での確認
CI 環境だけでなく、ローカル環境でも同じレポートを確認できます。以下は、Docker Compose を使って Jaeger を起動し、Jest のトレース結果を可視化する例です。
Docker Compose 設定
yaml# docker-compose.yml
version: '3'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- '16686:16686' # Jaeger UI
- '4318:4318' # OTLP HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
Jaeger を起動
bashdocker-compose up -d
Jest をトレースモードで実行
bashexport OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_SERVICE_NAME=jest-tests
yarn test:trace
Jaeger UI でトレースを確認
ブラウザで http://localhost:16686 にアクセスし、jest-tests サービスを選択すると、テスト実行のトレース結果が表示されます。各テストケースの実行時間や、セットアップ処理のボトルネックを視覚的に確認できます。
ダッシュボードの例
以下は、JUnit XML、SARIF、OpenTelemetry を統合した CI ダッシュボードのイメージです。
GitHub Actions のサマリー画面
yaml✅ ESLint: 12 warnings, 0 errors
✅ npm audit: 0 vulnerabilities
✅ Jest Tests: 150 passed, 2 failed
❌ UserService › should handle invalid email
❌ OrderService › should cancel order
📊 Coverage: 85.3%
📈 Trace: View in Jaeger
GitHub Security タブ
javaCode scanning alerts
─────────────────────────────────────────
🔍 ESLint
- no-unused-vars: 'unusedVar' is defined but never used (src/userService.ts:42)
- @typescript-eslint/no-explicit-any: Unexpected any (src/orderService.ts:78)
🔒 npm audit
- axios: Regular Expression Denial of Service (CVE-2023-12345)
- lodash: Prototype Pollution (CVE-2023-67890)
Jaeger UI のトレースビュー
javajest-tests
├─ beforeAll: setup database (125ms)
├─ test: should authenticate user (45ms)
│ ├─ database query: SELECT * FROM users (20ms)
│ └─ password validation (15ms)
├─ test: should handle invalid email (12ms)
└─ afterAll: cleanup (34ms)
このように、JUnit XML、SARIF、OpenTelemetry を組み合わせることで、テスト結果、セキュリティ、パフォーマンスを一元的に管理できます。
まとめ
Jest を可観測化することで、テストの品質とパフォーマンスを継続的に改善できます。JUnit XML でテスト結果を構造化し、SARIF でセキュリティレポートを統合し、OpenTelemetry でパフォーマンスをトレースすることで、CI ダッシュボードでの一元管理が実現します。
本記事で紹介した手法
| # | フォーマット | 導入方法 | 活用例 |
|---|---|---|---|
| 1 | JUnit XML | jest-junit レポーターを追加 | GitHub Actions でテスト結果を表示 |
| 2 | SARIF | ESLint、npm audit の結果を変換 | GitHub Security タブで脆弱性を確認 |
| 3 | OpenTelemetry | SDK を初期化し、カスタムスパンを追加 | Jaeger でトレースを可視化 |
これらの手法を組み合わせることで、テストの成功率、セキュリティリスク、パフォーマンスのボトルネックを俯瞰でき、チーム全体の開発効率が向上します。
可観測性の導入は、一度設定すれば継続的に価値を生み出します。まずは JUnit XML から始めて、徐々に SARIF や OpenTelemetry を追加していくと良いでしょう。
関連リンク
articleJest を可観測化する:JUnit/SARIF/OpenTelemetry で CI ダッシュボードを構築
articleJest でプロパティベーステスト:fast-check で仕様を壊れにくくする設計
articleJest expect.extend チートシート:実務で使えるカスタムマッチャー 40 連発
articleJest を Yarn PnP で動かす:ゼロ‐node_modules 時代の設定レシピ
articleJest の TS 変換速度を検証:ts-jest vs babel-jest vs swc-jest vs esbuild-jest
articleJest で ESM が通らない時の解決フロー:type: module/transform/resolver を総点検
articleJotai 運用ガイド:命名規約・debugLabel・依存グラフ可視化の標準化
articleZod vs Ajv/Joi/Valibot/Superstruct:DX・速度・サイズを本気でベンチ比較
articleYarn でモノレポ設計:パッケージ分割、共有ライブラリ、リリース戦略
articleJest を可観測化する:JUnit/SARIF/OpenTelemetry で CI ダッシュボードを構築
articleGitHub Copilot 利用可視化ダッシュボード:受容率/却下率/生成差分を KPI 化
articleWeb Components vs Lit:素の実装とフレームワーク補助の DX/サイズ/速度を実測比較
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来