testing について
Rails 開発者向けのテストスキルで、テスト種別の選択、スイートの構成、問題の調査を扱います。テストピラミッドを重視し、可能な場合はブラウザテストよりリクエストテストと fixtures を勧めます。
主な機能
- 01適切な Rails テスト種別の選択
- 02リクエスト・システムテストの作成
- 03並列テストと CI の設定
- 04DB 問題や不安定なテストの調査
- 05fixtures によるテストデータ管理
活用シーン
- 01Rails テストスイートの構築
- 02HTTP エンドポイントのリクエストテスト追加
- 03並列テストや CI の設定
- 04不安定なシステムテストの調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill testingインストール後、Claude Code で/testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/ci.mdfixtures.mdhelpers-and-assertions.mdparallel-testing.mdrequest-tests.mdsystem-tests.md詳細
Rails テストエキスパート
Rails アプリケーション向けに、高速で信頼性が高く、保守しやすいテストスイートを構築します。このスキルでは、Rails のテストエコシステム、つまりテストの種類、インフラストラクチャ、どこで何をテストするかを扱います。
関連スキル: Minitest 固有のアサーション、フィクスチャの詳細、TDD ワークフロー、RSpec から Minitest への変換については、minitest スキルを参照してください。
このスキルを使う場面
- Rails のテストスイートをセットアップまたは設定するとき
- テストの種類(システム、リクエスト、モデル、ジョブ、メーラーなど)を選ぶとき
- Capybara を使ってシステムテストを書くとき
- 並列テストや CI パイプラインを設定するとき
- 不安定なテストやテストデータベースの問題をデバッグするとき
- 新機能のテストカバレッジを追加するとき
基本方針
テストピラミッドを守る
/\ System Tests (FEW — critical user paths only)
/ \
/____\ Request/Integration Tests (MODERATE)
/ \
/________\ Unit Tests (MANY — models, services, policies, jobs)
エージェントによるミスのほとんどは、このピラミッドを逆転させることから生じます。 あらゆる機能のシステムテストを書くことは、最もよくある間違いです。システムテストはユニットテストより10〜100倍遅くなります。不安定なシステムテストが1つあるだけで、モデルテスト50件分以上の時間が無駄になります。
基本ルール
- 実装ではなく動作をテストする — メソッド呼び出しではなく、結果を検証します
- ファクトリーではなくフィクスチャを使う — 10〜100倍高速で、連鎖的なレコード作成がありません
- システムテストよりリクエストテストを優先する — JavaScript に依存するフローに限り、システムテストを使います
- 各テストを独立させる — 共有状態やテスト実行順への依存をなくします
- 包括的なカバレッジより高速なフィードバックを重視する — 頻繁に実行される高速なスイートは、実行されない低速なスイートに勝ります
適切なテストの種類を選ぶ
これは最も重要な判断です。選択を誤ると、低速でもろいテストを書くことになり、時間を無駄にします。
| テストの種類 | 基底クラス | 速度 | 用途 |
|---|---|---|---|
| モデル | ActiveSupport::TestCase | ⚡ 高速 | バリデーション、スコープ、ビジネスロジック、コールバック |
| サービス/PORO | ActiveSupport::TestCase | ⚡ 高速 | サービスオブジェクト、フォームオブジェクト、クエリオブジェクト |
| ジョブ | ActiveJob::TestCase | ⚡ 高速 | ジョブの動作、エンキュー、リトライ |
| メーラー | ActionMailer::TestCase | ⚡ 高速 | メールの内容、宛先、添付ファイル |
| ヘルパー | ActionView::TestCase | ⚡ 高速 | ビューヘルパーの出力 |
| リクエスト | ActionDispatch::IntegrationTest | 🔶 中速 | HTTP リクエスト/レスポンスのサイクル、API エンドポイント |
| インテグレーション | ActionDispatch::IntegrationTest | 🔶 中速 | ブラウザーを使わない複数ステップのワークフロー |
| チャンネル | ActionCable::Channel::TestCase | ⚡ 高速 | チャンネルのサブスクリプション、ストリーム |
| コネクション | ActionCable::Connection::TestCase | ⚡ 高速 | WebSocket 認証、接続識別子 |
| システム | ActionDispatch::SystemTestCase | 🐌 低速 | JavaScript 操作、重要な E2E ユーザーフロー |
判断フロー
Does it test business logic with no HTTP needed?
→ Model / Service test
Does it test an HTTP endpoint's response?
→ Request test
Does it require a real browser (JS, complex UI)?
→ System test (keep these minimal)
Does it test email content or delivery?
→ Mailer test
Does it test a background job?
→ Job test
Does it test a view helper method?
→ Helper test
Does it test a WebSocket channel?
→ Channel test
テストディレクトリの構成
test/
├── test_helper.rb # Global config
├── application_system_test_case.rb # System test base (created on first system test)
├── controllers/ # Controller/functional tests (legacy — prefer request tests)
├── fixtures/ # YAML fixture data
│ ├── users.yml
│ ├── files/ # File fixtures for uploads
│ └── action_text/rich_texts.yml # Action Text fixtures
├── helpers/ # View helper tests
├── integration/ # Integration tests
├── jobs/ # Job tests
├── mailers/ # Mailer tests
│ └── previews/ # Mailer previews (not tests, but useful)
├── models/ # Model tests (bulk of your tests)
├── channels/ # Action Cable channel tests
│ └── application_cable/ # Connection tests
├── requests/ # Request tests (preferred over controllers/)
├── services/ # Service object tests
├── system/ # System tests (keep small)
└── support/ # Shared test helpers
テストランナー
基本的なコマンド
# Run everything (excludes system tests)
bin/rails test
# Run system tests separately
bin/rails test:system
# Run ALL tests including system
bin/rails test:all
# Run a single file
bin/rails test test/models/user_test.rb
# Run a specific test by line number
bin/rails test test/models/user_test.rb:42
# Run a line range
bin/rails test test/models/user_test.rb:10-30
# Run by name pattern
bin/rails test -n /password/
# Run a directory
bin/rails test test/models/
# Fail fast — stop on first failure
bin/rails test --fail-fast
# Verbose output
bin/rails test -v
# Profile slow tests
bin/rails test --profile
# Set seed for reproducibility
bin/rails test --seed 12345
主なフラグ
| フラグ | 用途 |
|---|---|
-n PATTERN | 名前でテストを絞り込みます(文字列または/regex/) |
-f / --fail-fast | 最初の失敗で停止します |
-v / --verbose | 各テスト名を表示します |
-b / --backtrace | アプリケーションの行だけでなく、完全なバックトレースを表示します |
--profile [N] | 実行時間が最も長いテストをN件表示します(既定値は10) |
-p / --pride | 虹色の出力 🌈 |
-S / --skip CODES | 報告する種類をスキップします(例:エラーの場合はE) |
リクエストテスト(HTTP テストで推奨)
リクエストテストは、ミドルウェアスタックを通じた完全な HTTP リクエストをシミュレートします。新しいコードでは、コントローラーテストの代わりにこれを使ってください。
class ArticlesRequestTest < ActionDispatch::IntegrationTest
setup do
@user = users(:admin)
@article = articles(:published)
end
test "GET /articles returns success" do
get articles_url
assert_response :success
end
test "POST /articles creates with valid params" do
sign_in @user
assert_difference "Article.count", 1 do
post articles_url, params: { article: { title: "New", body: "Content" } }
end
assert_redirected_to article_url(Article.last)
end
test "POST /articles rejects invalid params" do
sign_in @user
assert_no_difference "Article.count" do
post articles_url, params: { article: { title: "" } }
end
assert_response :unprocessable_entity
end
end
リクエスト後に利用できるもの
response/response.body/response.parsed_bodycookies、flash、session@controller、@request、@response
JSON API テスト、Turbo Stream テスト、ファイルアップロード、認証パターン、DOM アサーションについては、
references/request-tests.mdを参照してください。
システムテスト(必要な場合に限って使用)
システムテストは、Capybara を通じて実際のブラウザーで実行します。JavaScript や複雑な UI 操作が必要な重要なフローに限って使用してください。
システムテストを書く場合
✅ ユーザー登録/ログインのフロー ✅ チェックアウト/決済のフロー ✅ 複雑な JS インタラクション(ドラッグ&ドロップ、モーダル、ライブ検索) ✅ Turbo/Stimulus に依存する機能 ✅ プレビュー付きのファイルアップロード
システムテストを書かない場合
❌ CRUD 操作(リクエストテストを使用)
❌ バリデーションエラーの表示(リクエストテストと assert_select を使用)
❌ API エンドポイント(リクエストテストを使用)
❌ ブラウザーなしでテストできるもの
設定
# test/application_system_test_case.rb
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
end
ドライバーのオプション:
| ドライバー | ブラウザー | 用途 |
|---|---|---|
:selenium + :headless_chrome | Chrome(ヘッドレス) | デフォルト、CI に適しています |
:selenium + :headless_firefox | Firefox(ヘッドレス) | 代替 |
:selenium + :chrome | Chrome(表示) | デバッグ |
:cuprite | CDP 経由の Chrome | より高速、Selenium のオーバーヘッドなし |
システムテストを書く
require "application_system_test_case"
class CheckoutTest < ApplicationSystemTestCase
test "user completes checkout" do
user = users(:buyer)
product = products(:widget)
visit new_session_path
fill_in "Email", with: user.email
fill_in "Password", with: "password"
click_on "Sign in"
visit product_path(product)
click_on "Add to Cart"
click_on "Checkout"
fill_in "Card number", with: "4242424242424242"
click_on "Pay"
assert_text "Order confirmed"
end
end
不安定なシステムテストを避ける
assert_text、assert_selectorは自動的に待機します。page.has_css?の代わりに使用してください- それでも不安定な場合は、明示的な待機を追加します:
assert_text "Done", wait: 5 sleepは絶対に使用しないでください。常に Capybara の待機対応アサーションを使用してください- デバッグには
take_screenshotを使用します。失敗したテストではtmp/screenshots/に自動でスクリーンショットが保存されます
Capybara DSL のリファレンス、Cuprite のセットアップ、JS インタラクションのパターン、Turbo/Stimulus のテストについては、
references/system-tests.mdを参照してください。
メーラーテスト
class UserMailerTest < ActionMailer::TestCase
test "welcome email" do
email = UserMailer.welcome(users(:new_user))
assert_emails 1 do
email.deliver_now
end
assert_equal ["[email protected]"], email.from
assert_equal [users(:new_user).email], email.to
assert_equal "Welcome!", email.subject
assert_match users(:new_user).name, email.body.to_s
end
end
リクエストテストから送信する場合: assert_emails 1 { post users_url, params: {...} }
キューに登録する場合: assert_enqueued_email_with UserMailer, :welcome, args: [user] { ... }
マルチパートメール、添付ファイル、パラメーター化されたメーラー、プレビューについては、
references/helpers-and-assertions.mdを参照してください。
ジョブテスト
class ProcessOrderJobTest < ActiveJob::TestCase
test "charges the account" do
perform_enqueued_jobs do
ProcessOrderJob.perform_later(orders(:pending))
end
assert orders(:pending).reload.charged?
end
end
ほかのコードからのキュー登録を検証する場合:
class OrderTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
test "#place enqueues processing job" do
assert_enqueued_with(job: ProcessOrderJob) do
orders(:new).place!
end
end
end
ジョブキューのアサーション、フィルタリング、チェーンジョブ、例外のテストについては、
references/helpers-and-assertions.mdを参照してください。
ヘルパーテスト
class CurrencyHelperTest < ActionView::TestCase
test "formats USD" do
assert_equal "$10.00", format_price(1000, "USD")
end
test "handles zero" do
assert_equal "$0.00", format_price(0, "USD")
end
end
Action Cable テスト
# Channel test
class ChatChannelTest < ActionCable::Channel::TestCase
test "subscribes to room stream" do
subscribe room: "general"
assert subscription.confirmed?
assert_has_stream "chat_general"
end
end
# Connection test
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
test "connects with valid user" do
cookies.signed[:user_id] = users(:admin).id
connect
assert_equal users(:admin).id, connection.current_user.id
end
test "rejects without auth" do
assert_reject_connection { connect }
end
end
ブロードキャストのアサーション(すべてのテストで使用可能): include ActionCable::TestHelper を追加し、assert_broadcast_on、assert_broadcasts を使用します。
ストリームのアサーション、カスタムチャンネルメソッド、モデルからのブロードキャストテストについては、
references/helpers-and-assertions.mdを参照してください。
テストデータベースの管理
スキーマの同期
# After migrations, sync test DB schema
bin/rails db:test:prepare
# Or recreate from scratch
bin/rails db:test:purge db:test:prepare
# Rails auto-checks for pending migrations before tests run
トランザクション
デフォルトでは、各テストはデータベーストランザクション内で実行され、完了後にロールバックされます。これにより、手動でクリーンアップしなくてもテストが分離されます。
必要に応じて無効にします(例:マルチスレッドコードのテスト):
class ThreadedTest < ActiveSupport::TestCase
self.use_transactional_tests = false
teardown do
# Manual cleanup needed!
User.delete_all
end
end
並列テスト
プロセスベース(デフォルト)
# test/test_helper.rb
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors)
end
ワーカーごとに個別のテストデータベース(test-database-0、test-database-1 など)を作成します。
# Override worker count
PARALLEL_WORKERS=4 bin/rails test
スレッドベース
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors, with: :threads)
end
JRuby/TruffleRuby を使用する場合や、プロセスのフォークに時間がかかる場合に使用します。
ワーカーごとのセットアップには、parallelize_setup/parallelize_teardown フックを使用します。デフォルトのしきい値は 50 テストです(それ未満では並列化されません)。config.active_support.test_parallelization_threshold = 100 でカスタマイズできます。
プロセスとスレッドの比較、フック、並列テストでよくある問題については、
references/parallel-testing.mdを参照してください。
CI の設定
# config/environments/test.rb
config.eager_load = ENV["CI"].present?
# CI steps
bin/rails db:test:prepare
bin/rails test # Unit + request tests
bin/rails test:system # System tests (separate job recommended)
CI のヒント: PARALLEL_WORKERS を使用して並列化する、システムテストを専用の CI ジョブに分ける、gem/node_modules をキャッシュする、ヘッドレス Chrome を使用する、Zeitwerk の eager-load テストを追加する。
GitHub Actions の YAML 全体と CI のパターンについては、
references/ci.mdを参照してください。
時間に依存するテスト
freeze_time do ... end # Freeze at current moment
travel_to Time.zone.local(2024, 1, 15) do ... end # Travel to specific time
travel 3.days do ... end # Travel forward
travel_back # Return to real time
時間テストの詳しいパターン、よくある落とし穴、不安定なテストの修正については、
references/parallel-testing.mdを参照してください。
避けるべきアンチパターン
- システムテストが多すぎる — ブラウザーが不要なら、リクエストテストを使います
- Rails 自体をテストする —
validates :name, presence: trueが機能することをテストしてはいけません。テストするのは独自のビジネスロジックです - 実装をテストする —
assert_received(:method)はテストを内部実装に結び付けます - ファクトリーの連鎖 — Factory Bot が1つのテストのために15件のレコードを作成する場合は、フィクスチャを使います
- 変更可能な状態の共有 — あるテストで設定したインスタンス変数が別のテストに漏れます
sleepによる待機 — システムテストでsleep 2を使わず、Capybara 組み込みの待機機能を使います- タイムスタンプの厳密なアサーション —
assert_equal Time.current, record.created_atは不安定です - プライベートメソッドのテスト — テストするのはパブリックインターフェースだけです
- 過剰なモック — すべてをモックすると、コードではなくモックをテストすることになります
- 否定的なテストがない — 失敗または拒否されるべきケースも必ずテストします
クイックリファレンス:何をどこでテストするか
| 対象 | テストの種類 | 例 |
|---|---|---|
| モデルのバリデーション | モデルテスト | refute User.new(email: nil).valid? |
| モデルのスコープ | モデルテスト | assert_includes User.active, @user |
| ビジネスロジックのメソッド | モデル/サービスのテスト | assert_equal 42, order.total |
| API エンドポイント | リクエストテスト | get api_users_url, as: :json |
| ページが正しくレンダリングされること | リクエストテスト | get root_url; assert_response :success |
| フォーム送信 | リクエストテスト | post articles_url, params: {...} |
| 認証/認可 | リクエストテスト | get admin_url; assert_response :redirect |
| メールの内容 | メーラーのテスト | assert_equal "Welcome", email.subject |
| メールが送信されること | リクエストテスト | assert_emails 1 { post ... } |
| ジョブの動作 | ジョブテスト | perform_enqueued_jobs { MyJob.perform_later } |
| ジョブがエンキューされること | モデル/リクエストテスト | assert_enqueued_with(job: MyJob) { ... } |
| JS 依存のフロー | システムテスト | click_on "Submit"; assert_text "Done" |
| ビューヘルパー | ヘルパーテスト | assert_equal "$10", format_price(10) |
| WebSocket チャネル | チャネルテスト | subscribe; assert_has_stream "room_1" |