mcpField
R
rspec-best-practices

rspec-best-practices

github.com/midudevテスト・品質7.0k62 回インストール1 週間前に更新
リポジトリ

rspec-best-practices について

rspec-best-practicesは、RSpecテストの作成、レビュー、整理を支援するスキルです。テスト構成、ファクトリー、モック、決定性、TDDの手順を示します。

主な機能

  1. 01テスト種別と構成の指針
  2. 02振る舞いを重視したアサーション
  3. 03最小限のファクトリーとモック
  4. 04決定的なテストの指針
  5. 05段階的なTDD手順
  6. 06出力規則と再利用可能なテンプレート

活用シーン

  1. 01Railsの振る舞いに対するRSpecテスト作成
  2. 02保守性を意識したスペックのレビュー
  3. 03不安定なテストや時刻依存テストの削減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/midudev/autoskills --skill rspec-best-practices

インストール後、Claude Code で/rspec-best-practicesを入力して読み込まれたか確認します。

同梱ファイルassets/spec_templates.mdEXAMPLES.md

詳細

RSpec のベストプラクティス

RSpec テストの作成、レビュー、整理を行う場合は、このスキルを使用してください。

基本原則: 実装への依存よりも、振る舞いへの確信を優先します。優れたスペックは読みやすく、決定的で、保守コストが低いものです。

クイックリファレンス

項目ルール
スペックの種類Model:ドメインロジック、Request:HTTP エンドポイント(controller より優先)、Job:バックグラウンド処理、Service/PORO:Rails ヘルパーなし、System:重要な E2E のみ(低速)
アサーション実装ではなく振る舞いをテストします
ファクトリー最小限にします — 必要な属性のみを指定し、省略可能な状態にはトレイトを使います。create より build/build_stubbed を優先します
モック内部コードではなく、外部との境界をスタブします
独立性各 example は独立させ、共有の可変状態を持たせません
命名クラス/メソッドには describe、シナリオには context を使います
Service spec必須: 主な呼び出しには describe '.call' と subject(:result) を使います
let と let!既定では let を使います。オブジェクトを example の実行前に存在させる必要がある場合に限り let! を使います
外部サービスのモックallow(ServiceClass).to receive(:method) を使います — instance_double は使いません。instance_double は注入された協力オブジェクトに限って使います
Example の名前現在形にします:it 'returns the user'。it 'should ...' は使いません。「and」という語を絶対に含めないでください — example を分割します
aggregate_failures1 つの example 内で複数の関連項目を検証する場合に使います

TDD ワークフロー

RSpec を使って新しい振る舞いを開発する場合は、次の手順に従ってください。

  1. 失敗するスペックを書く — 意図した振る舞いを検証できる、最小のスペック種別を選びます(model > service > request > system)。
  2. 実行して失敗メッセージを確認する — エラーはセットアップの問題ではなく、コードが不足していることを示す必要があります。
  3. スペックを通すために必要最小限のコードを実装する。
  4. リファクタリングする — テストスイートを成功状態に保ちながら、重複と命名を整理します。
  5. 検証する — コミットする前に、関連するスペックファイル全体を実行し、続けてテストスイート全体を実行します。
Rails の変更で最初に書くべき失敗スペックを選ぶ
変更の種類最初に書くもの
純粋なドメインロジックModel または PORO の service spec
HTTP エンドポイントの振る舞いRequest spec
バックグラウンド処理Job spec
レイヤーをまたぐユーザージャーニーSystem spec(控えめに使用)

ファクトリーの設計

ファクトリーは最小限にします。ビジネスロジックにファクトリーのデフォルト値を決して頼らないでください — 明示的に設定するか、トレイトを使います。build/build_stubbed で十分な場合は create を避けます。

Service spec(基本パターン)

RSpec.describe Invoices::MarkOverdue do
  describe '.call' do
    subject(:result) { described_class.call(invoice: invoice) }

    context 'when the invoice is overdue and unpaid' do
      let(:invoice) { create(:invoice, due_date: 2.days.ago, paid_at: nil) }

      it 'marks the invoice overdue' do
        expect { result }.to change { invoice.reload.overdue? }.from(false).to(true)
      end
    end

    context 'when the invoice is already paid' do
      let(:invoice) { create(:invoice, due_date: 2.days.ago, paid_at: 1.day.ago) }

      it 'does not change the invoice' do
        expect { result }.not_to change { invoice.reload.updated_at }
      end
    end
  end
end

→ 詳細な例:EXAMPLES.md | コピーして使えるテンプレート:assets/spec_templates.md

Shared Examples

example ごとの let の上書きなしに、同じ振る舞いの契約が複数の対象に適用される場合に限り使用します。各 context で異なるセットアップが必要になる場合は避けてください — それは抽象化が適切でない兆候です。→ EXAMPLES.md の例

1 つの example に 1 つの振る舞い — Example の名前に「and」を絶対に含めない

it/specify の説明に 「and」 という語が含まれている場合、その example は 2 つの振る舞いを検証しています。分割してください。example ごとに 1 つの振る舞いにします。これは model、request、service、job、mailer、system のすべてのスペック種別に適用されます。

# BAD — two assertions; if the first fails, the second never runs
it 'returns 201 and creates the record' do; end
it 'saves the order and sends the confirmation email' do; end
it 'updates the user and logs the change' do; end

# GOOD — one observable outcome per example
it 'returns 201' do; end
it 'creates the record' do; end

it 'saves the order' do; end
it 'sends the confirmation email' do; end

スペックを確定する前のセルフチェック: すべての it '...'/it "..."/specify '...' 文字列を検索し、「and」という語(大文字と小文字を区別せず、単語境界で判定)がないか確認してください。該当箇所はすべて分割します。「returns nil and does not raise」のような「利便性」のための example であっても例外はありません。

出力スタイル

RSpec のスペックの作成またはレビューを求められた場合、出力は以下の各ルールを必ず満たしてください。各ルールは個別に評価され、違反が1つでもあるとチェック全体に失敗します。

  1. スペックファイルのパスはソースを反映します:app/foo/bar.rb → spec/foo/bar_spec.rb。
  2. **# frozen_string_literal: true**を各スペックファイルの1行目に記述します。
  3. **RSpec.describe**には完全な定数パス(RSpec.describe Module::Class do)を使用し、文字列は使用しません。
  4. テスト対象の各メソッドには**describe '#method' / describe '.class_method'**を使用します。
  5. シナリオの違いには**context 'when ...' / context 'with ...'**を使用します。メソッドをまとめるためにcontextを使用してはいけません。
  6. テストデータにはletを使用し、let!はテスト対象のアクションより前にオブジェクトが存在している必要がある場合にのみ使用します。
  7. プロジェクトがすでにtest-profに依存している場合を除き、let_it_beは使用しません(まずGemfile.lockを確認してください)。
  8. すべての例の説明文で「and」を使用してはいけません。出現するたびに分割してください(上のセクションを参照)。最も見落とされやすいルールです。スペックを返す前に、明示的に確認してください。
  9. .callを呼び出すサービス/POROのスペックでは**subject(:result) { ... }**を使用します。
  10. 時間に依存するアサーションには**travel_to / freeze_time**を使用します。過去のTime.nowを設定したり、Time.currentを直接スタブしたりしてはいけません。
  11. 外部との境界はクラスメソッドレベルでモックします(allow(SomeClient).to receive(:method))。ActiveRecordのファインダーは決してモックしません。

不安定なテストと決定的なアサーション

原因修正方法
時間依存のロジックfreeze_time / travel_toを使用します。手軽な方法として過去の日付を設定してはいけません
状態の漏れ各例で独自の状態をセットアップします。before(:all)は避けてください
非同期ジョブqueue_adapter = :test + have_enqueued_jobを使用します。副作用を命令的にアサートしてはいけません
外部HTTPWebMock / VCRを使用します。CIで実際のネットワーク通信を許可してはいけません
DB状態の混入トランザクションフィクスチャまたはDatabaseCleanerを使用します。コンテキスト間でlet!を共有してはいけません
競合状態Capybaraの明示的な待機を使用します。sleepは避けてください
精度の低いアサーション最終状態ではなくchange.from().to()を使用します。be_truthy / be_falseyではなく正確な値を使い、updated_atをアサートしてはいけません

アセット

コメント