Ruby on Rails testing patterns based on official Rails testing guide, t-wada's TDD philosophy, and DHH's testing stance (Rails 8.1+: system tests dropped from generators). Covers Minitest, fixtures, integration tests, and test architecture.
From eccnpx claudepluginhub tatematsu-k/ai-development-skills --plugin eccThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Based on: Rails Testing Guide, t-wada (和田卓人), DHH, and Rails core team.
和田卓人(t-wada)は日本における TDD の第一人者。Kent Beck の「テスト駆動開発」翻訳者。
Google のテストサイズ分類を推奨:
/ Large \ ← 少数: ブラウザ、外部API、複数サービス
/ Medium \ ← 中程度: DB、ファイルシステム
/ Small \ ← 大量: 単一プロセス、高速、決定的
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
アイスクリームコーンからピラミッドへ: 最初は E2E が多くても良い。徐々にユニットテストの比率を増やしていく。
「AI コーディングエージェントの時代、自動テストは"ガードレール"として今まで以上に不可欠」 — t-wada, 2025
AI がテストを書くバリアを下げた今こそ、テストを標準にする好機。
test/
├── models/ # Model tests (Small)
├── controllers/ # Controller tests (Small/Medium)
├── integration/ # Integration tests (Medium)
├── system/ # System tests (Large) — Rails 8.1+ ではデフォルト生成されない
├── helpers/ # Helper tests
├── mailers/ # Mailer tests
├── jobs/ # Job tests
└── fixtures/ # YAML fixtures
| テスト種別 | 基底クラス | 用途 |
|---|---|---|
| Model | ActiveSupport::TestCase | バリデーション、スコープ、メソッド |
| Controller | ActionDispatch::IntegrationTest | リクエスト/レスポンス |
| Integration | ActionDispatch::IntegrationTest | 複数リクエストのフロー |
| System | ActionDispatch::SystemTestCase | ブラウザ操作 |
| Mailer | ActionMailer::TestCase | メール送信 |
| Job | ActiveJob::TestCase | ジョブ実行 |
class ArticleTest < ActiveSupport::TestCase
test "should not save article without title" do
article = Article.new
assert_not article.save, "Saved the article without a title"
end
test "should report error on empty title" do
article = Article.new
article.valid?
assert_includes article.errors[:title], "can't be blank"
end
end
Rails 公式のテストデータ管理方法。Factory より高速。
# test/fixtures/articles.yml
one:
title: Welcome to Rails
body: Hello world
author: david # references users fixture
two:
title: A second article
body: Another body
author: david
# テストでの使用
class ArticleTest < ActiveSupport::TestCase
test "can access fixture" do
article = articles(:one)
assert_equal "Welcome to Rails", article.title
end
end
ERB で動的データ:
<% 20.times do |i| %>
user_<%= i %>:
name: User <%= i %>
email: user<%= i %>@example.com
<% end %>
DHH (Rails 8.1): 「System tests は常にブリットル。Integration tests を使え」
class ArticleFlowTest < ActionDispatch::IntegrationTest
test "can create an article" do
get new_article_path
assert_response :success
post articles_path, params: {
article: { title: "New Article", body: "Content" }
}
assert_response :redirect
follow_redirect!
assert_response :success
assert_dom "h1", "New Article"
end
test "requires authentication" do
get articles_path
assert_redirected_to login_path
end
end
class UserTest < ActiveSupport::TestCase
test "should validate presence of email" do
user = User.new(name: "Test")
assert_not user.valid?
assert_includes user.errors[:email], "can't be blank"
end
test "should validate uniqueness of email" do
existing = users(:david)
user = User.new(email: existing.email)
assert_not user.valid?
assert_includes user.errors[:email], "has already been taken"
end
test "should calculate full name" do
user = users(:david)
assert_equal "David H.", user.full_name
end
end
class ArticlesControllerTest < ActionDispatch::IntegrationTest
setup do
@article = articles(:one)
sign_in users(:david) # ヘルパー
end
test "should get index" do
get articles_path
assert_response :success
end
test "should create article" do
assert_difference("Article.count") do
post articles_path, params: {
article: { title: "Test", body: "Content" }
}
end
assert_redirected_to article_path(Article.last)
end
test "should not create article with invalid params" do
assert_no_difference("Article.count") do
post articles_path, params: {
article: { title: "", body: "" }
}
end
assert_response :unprocessable_entity
end
end
class UserMailerTest < ActionMailer::TestCase
test "welcome email" do
user = users(:david)
email = UserMailer.with(user: user).welcome_email
assert_emails 1 do
email.deliver_now
end
assert_equal ["no-reply@example.com"], email.from
assert_equal [user.email], email.to
assert_equal "Welcome", email.subject
assert_match "Hi #{user.name}", email.body.encoded
end
end
メールプレビュー: test/mailers/previews/ で /rails/mailers/ にアクセスして視覚確認
class CleanupJobTest < ActiveJob::TestCase
test "enqueues cleanup" do
assert_enqueued_with(job: CleanupJob) do
CleanupJob.perform_later
end
end
test "performs cleanup" do
assert_difference("OldRecord.count", -5) do
perform_enqueued_jobs do
CleanupJob.perform_later
end
end
end
end
DHH (Rails 8.1): 「System tests は機能しなかった。常にブリットルで、常に壊れ、常に遅い」
Rails 8.1 ではジェネレータからデフォルト削除。クリティカルなユーザーフローのみに限定。
class ArticleSystemTest < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
test "visiting the index" do
visit articles_path
assert_selector "h1", text: "Articles"
end
end
t-wada + Rails の統合アプローチ:
ユーザーとして、記事を作成したい。
そうすることで、知識を共有できる。
class ArticleCreationTest < ActionDispatch::IntegrationTest
test "user can create article with valid params" do
sign_in users(:david)
post articles_path, params: {
article: { title: "TDD in Rails", body: "Content" }
}
assert_response :redirect
assert_equal "TDD in Rails", Article.last.title
end
test "user cannot create article without title" do
sign_in users(:david)
post articles_path, params: {
article: { title: "", body: "Content" }
}
assert_response :unprocessable_entity
end
test "unauthenticated user cannot create article" do
post articles_path, params: {
article: { title: "Test", body: "Content" }
}
assert_redirected_to login_path
end
end
bin/rails test test/integration/article_creation_test.rb
class ArticlesController < ApplicationController
before_action :authenticate_user!
def create
@article = current_user.articles.build(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
private
def article_params
params.expect(article: [:title, :body])
end
end
bin/rails test test/integration/article_creation_test.rb
# All green
# DOM assertions
assert_dom "h1", "Expected Title"
assert_dom "tr", count: 3
# Response
assert_response :success # 200
assert_response :redirect # 3xx
assert_response :not_found # 404
assert_redirected_to article_path(@article)
# Database changes
assert_difference("Article.count") { post articles_path, params: {...} }
assert_no_difference("Article.count") { post articles_path, params: {...} }
# Emails
assert_emails 1 { UserMailer.welcome.deliver_now }
assert_enqueued_emails 1 { UserMailer.welcome.deliver_later }
# Jobs
assert_enqueued_with(job: CleanupJob) { CleanupJob.perform_later }
bin/rails test # 全テスト
bin/rails test test/models/article_test.rb # ファイル指定
bin/rails test test/models/article_test.rb:14 # 行番号指定
bin/rails test -f # fail-fast
bin/rails test:system # system tests のみ
bin/rails test -n /search/ # 名前でフィルタ
| Anti-Pattern | 推奨 |
|---|---|
| 「後でテストを書く」 | テストファースト (TDD) |
| System tests を多用 | Integration tests を中心に |
| テスト間の依存 | 各テストが独立(fixtures + transactional tests) |
| 実装の詳細をテスト | ユーザーから見える振る舞いをテスト |
| Factory の乱用(遅い) | Fixtures(高速、Rails標準) |
| テストカバレッジ = テスト品質 | カバレッジは指標の一つ。振る舞いの網羅が重要 |
| 全テストスイートを毎回実行 | 変更に影響するテストを優先 (Aaron Patterson) |
t-wada が推奨するアサーション失敗時の情報表示:
# power_assert gem
assert do
user.articles.published.count == 3
# | | | |
# | | 2 false
# | [#<Article...>, #<Article...>]
# #<User id: 1>
end
失敗時に中間値が表示され、デバッグが容易になる。
Sources: