rails-testing について
rails-testingは、Minitestとフィクスチャを用いたRailsアプリのテスト作成・レビュー向けスキルです。動作を重視したカバレッジ、決定性、適切なテスト層の選択を扱います。
主な機能
- 01Minitestとフィクスチャの規約
- 02動作を重視したテストカバレッジ
- 03テスト層の選び方
- 04非同期処理と外部境界のテスト
- 05認可とテナントのテストパターン
活用シーン
- 01Railsテストカバレッジのレビュー
- 02ユーザーワークフローの一連のテスト
- 03認可の回帰テスト追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ marckohlbrugge/ 37signals-skills --skill rails-testingインストール後、Claude Code で/rails-testingを入力して読み込まれたか確認します。
詳細
Rails テスト
テスト作成やテストレビューのタスクで使用します。Campfire と Fizzy のテストスイートに基づくパターンです。
デフォルト
- Minitest とフィクスチャを使用します。RSpec と FactoryBot は使用しません。
- 実装の詳細ではなく、振る舞いをテストします。
- テストは決定的かつ高速に保ちます。
parallelize(workers: :number_of_processors)を使用します。 - テストは機能と同じコミット/PR に含めます。先でも後でもありません。セキュリティ修正には必ず回帰テストを含めます。
- テストしやすさのために、本番コードを複雑にすることは避けます(テストのために設計を損なわないでください)。
カバレッジの配分(37signals が実際に力を入れているところ)
- 重点: モデルテスト(ドメインの不変条件、Concern)とコントローラー/インテグレーションテスト(リクエストの全サイクル、認証、フォーマット)。
- 軽め: 重要なハッピーパスを対象とするシステムテストを少数(スモークテスト1つで、登録から利用までをカバーできます)。ジョブテストは実際のロジックがあるジョブのみを対象にします。
- なし: ビューテスト、JS/Stimulus のユニットテスト、網羅的なチャネルテスト。UI の振る舞いはシステムテストで間接的にカバーします。
- 同じ振る舞いのアサーションを複数の層で重複させないでください。
フィクスチャ
- ID ではなくラベルで関連を表現します。相対的なタイムスタンプ(
created_at: <%= 1.hour.ago %>)や、共有する計算済みの値(再利用する bcrypt ダイジェスト1つ)には ERB を使用します。 app/modelsの構成をtest/modelsに反映します。app/models/card/closeable.rb↔test/models/card/closeable_test.rb、共有 Concern はtest/models/concerns/に配置します。- UUID の主キーはフィクスチャの順序に影響します。フィクスチャではラベルから生成した決定的な UUIDv7 を使い、
.first/.lastの順序を安定させ、実行時に作成されるレコードが常に新しくなるようにします。 - リッチコンテンツのフィクスチャは、手書きのマークアップではなく本番コード(
ActionText::Attachment.from_attachable(user).to_html)で作成します。
優れたプラクティス
- ユーザーのワークフローにはシステム/インテグレーションテストを、ドメインの不変条件にはモデルテストを使用します。
- 時間に依存するロジックには
travel_toを使用します。 - モック/スタブを使用するのは境界(外部 API、ネットワーク、時間、
SecureRandom)に限ります。外部 HTTP には VCR を使用し、クラス名とテスト名からカセット名を自動生成して、照合時にはタイムスタンプを正規化します。 - モデルテストから
perform_enqueued_jobs(only: SpecificJob)とassert_enqueued_withを使って非同期の副作用をテストします。単純なジョブクラスをユニットテストする方法は避けます。 - 関連する件数の変化は1つのアサーションで検証します。
assert_difference({ -> { card.assignees.count } => -1, -> { Event.count } => +1 })。 - コントローラーが複数のレスポンス形式を提供する場合は、両方をテストします。
as: :turbo_stream(ストリームのターゲットを検証)とas: :json(ステータス、Location ヘッダー、本文)。 - Turbo/ブロードキャストのアサーションは層ごとに使い分けます。モデルテストでは
assert_turbo_stream_broadcasts、コントローラーテストではassert_turbo_stream action:, target:、否定のケースではassert_no_turbo_stream_broadcastsを使用します。 - 認可テストでは、アクセスを許可するケースだけでなく、許可されない範囲も検証します。別テナント/別ロールからのアクセスが、許可されたアクセスの確認だけで終わらず、403/404 を返すことを検証します。
- マルチテナントのテストスイートでは、セットアップ時に
Current.account(振る舞いが実行者に依存する場合はCurrent.sessionも)を設定し、インテグレーション/システムテストではdefault_url_options[:script_name]を設定します。認証ルート用にuntenanted { }ヘルパーを用意します。ティアダウンでCurrentをクリアします。 - ミドルウェアは
Rack::MockRequestを使って単独でテストします。 - システムテストでは、複数ユーザーのシナリオに
using_session("Kevin")を使用し、リアルタイム機能を検証する前にケーブル接続を待ちます。認証には高速なセッション転送ヘルパーを使い、ログインの全フローは1つのスモークテストに限定します。 - FTS テーブルのようにトランザクション外の副作用があるテストスイートでは、ヘルパー単位でオプトアウトします。
self.use_transactional_tests = falseを設定し、明示的にクリーンアップします。 - 並列実行するテストスイートでは、スレッドプール、
ActionCable.server.pubsub、Currentなどの共有グローバル状態をテストごとにリセットします。
注意すべき点
- テストしやすさだけを目的に本番コードを複雑にすること。
- アプリ内部のコードを過剰にモックすること。
- 同じ振る舞いを複数の層で重複してテストすること。
- 各テストで不要なセットアップを繰り返し、テストスイートを遅くすること(そのためにフィクスチャがあります)。
- 1行だけのジョブクラスや単純な委譲をユニットテストすること。
- 本番のレンダラー/ヘルパーなら自動的に同期を保てる箇所に、HTML 文字列を手書きすること。
travel_toを使わずに、時間に依存するアサーションを行うこと。
コメント
Markdown 対応