テスト戦略とCI
ストーリー
「CIパイプラインの核心はテストだ。テスト戦略なしにCIは成り立たない」
木村先輩がテストレポートを画面に表示した。
「うちのプロジェクトでは、テストカバレッジ80%を必須にしている。 でも大事なのは数字じゃない。どの種類のテストを、 いつ、どれだけ実行するかの戦略だ」
「戦略......具体的にはどう設計するんですか?」
「テストピラミッドをベースに、CIの各段階で実行するテストを 明確に分けるんだ。速いテストは毎回、遅いテストは必要な時だけ。 この使い分けが、開発速度と品質のバランスを保つ鍵になる」
テストの種類とCIでの配置
テストピラミッドとCIパイプライン
テストピラミッド × CIパイプライン
/\
/ \ E2Eテスト
/ E2E\ → デプロイ前のみ実行
/------\ → ブラウザ操作を自動化
/ 結合 \ 結合テスト
/ テスト \ → PRマージ時に実行
/------------\ → API呼び出し、DB接続を含む
/ 単体テスト \ 単体テスト
/________________\ → 毎push/毎PRで実行
→ 外部依存なし、高速
CIパイプラインでの実行タイミング
| テスト種類 | 実行タイミング | 実行時間目安 | ツール例 |
|---|---|---|---|
| Lint/静的解析 | 毎push | 1〜2分 | ESLint, Prettier |
| 単体テスト | 毎push | 2〜5分 | Jest, Vitest |
| 結合テスト | PR作成・更新時 | 5〜10分 | Supertest, TestContainers |
| E2Eテスト | mainへのマージ前 | 10〜30分 | Playwright, Cypress |
| パフォーマンステスト | 定期実行(schedule) | 15〜60分 | k6, Artillery |
| セキュリティスキャン | 定期実行(schedule) | 5〜15分 | Snyk, Trivy |
CIでのテスト自動化パターン
パターン1: 段階的テスト実行
yaml
name: Staged Testing
on:
pull_request:
branches: [ main ]
jobs:
# Stage 1: 最速のチェック
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
# Stage 2: 単体テスト
unit-test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run test:unit -- --coverage
- name: Check coverage threshold
run: |
COVERAGE=$(cat coverage/coverage-summary.json | \
jq '.total.lines.pct')
echo "Coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage below 80%!"
exit 1
fi
# Stage 3: 結合テスト
integration-test:
needs: unit-test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run integration tests
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test
run: npm run test:integrationサービスコンテナの活用
CIでデータベースやキャッシュなどの外部サービスを使うには、services を定義します。
yaml
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5テストカバレッジの管理
カバレッジレポートの生成と確認
yaml
- name: Run tests with coverage
run: npm test -- --coverage --coverageReporters=json-summary
- name: Display coverage summary
run: |
echo "## Coverage Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Coverage |" >> $GITHUB_STEP_SUMMARY
echo "|--------|----------|" >> $GITHUB_STEP_SUMMARY
cat coverage/coverage-summary.json | jq -r '
.total | to_entries[] |
"| \(.key) | \(.value.pct)% |"
' >> $GITHUB_STEP_SUMMARYカバレッジの閾値設定
json
// jest.config.js
{
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}Flakyテスト対策
不安定なテスト(Flaky Test)はCI の信頼性を損ないます。
Flakyテストの原因と対策
| 原因 | 対策 |
|---|---|
| タイミング依存 | 明示的なwaitやリトライ機構を使う |
| 外部サービス依存 | モックやサービスコンテナを使う |
| テスト間の依存 | 各テストを独立させる(setupで初期化) |
| ランダムなデータ | シード値を固定する |
| リソース競合 | 並列実行時のポート/ファイル衝突を避ける |
リトライ戦略
yaml
- name: Run tests with retry
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: npm testまとめ
| ポイント | 内容 |
|---|---|
| テストピラミッド | 単体テストを多く、E2Eテストは少なく |
| 段階的実行 | 速いテストを先に、遅いテストは後に |
| サービスコンテナ | DB/キャッシュをCIで使う方法 |
| Flakyテ スト | 不安定なテストはCI信頼性の敵 |
チェックリスト
- テストピラミッドに基づくCI戦略を設計できる
- サービスコンテナを使ったテスト環境の構築方法を理解した
- テストカバレッジの閾値設定と確認方法を把握した
- Flakyテストの原因と対策を説明できる
次のステップへ
次のセクションでは、マトリクスビルドと並列実行を学びます。 複数のNode.jsバージョンやOSで同時にテストを走らせ、パイプラインの効率を最大化しましょう。
推定読了時間: 30分