0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第22章|今さら学ぶ「テスト」

0
Last updated at Posted at 2026-02-26

第22章|今さら学ぶ「テスト」

📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ

この章でわかること

  • なぜテストを書くのか — 「安全ネット」としてのテスト
  • 単体 / 統合 / E2E テストの違い
  • TDDの流れ — Red → Green → Refactor の信号機
  • RSpec と Minitest — 2つのテストフレームワーク
  • FactoryBot — テスト用データの工場

🏠 たとえ話で掴む「テスト」

テストは 自動車の検査 にたとえるとわかりやすいです。

車を作ったら、いきなり公道に出しません。まず部品ごとの検査(ブレーキは効くか、エンジンは動くか)をして、次に組み立てた状態の検査(走れるか、曲がれるか)をして、最後に実際の道路を想定した試運転をします。

自動車の検査 テストの種類
部品検査(ブレーキ単体) 単体テスト (モデル単体)
組み立て検査(ブレーキ+車体) 統合テスト (コントローラ+モデル)
試運転(実際の道路で走る) E2Eテスト (ブラウザで操作)

テストを書いておくと、コードを変更したときに「壊れていないか」を一瞬で確認できます。手動で全画面を確認するのは非現実的ですが、テストなら数秒〜数分で完了します。


🧪 テストとは何か — 技術的な定義

テストとは、 コードが期待通りに動くことを、別のコードで自動的に検証する仕組み です。

手動テスト(画面を触って確認)との決定的な違いは、 繰り返し実行できる 点にあります。アプリの機能が10個のうちは手動でも確認できますが、100個、500個と増えたとき、1つの変更のたびに全機能を手作業で確認するのは不可能です。

テストを書く理由は大きく3つあります。

  1. リグレッション(退行)の防止 — 新しいコードを追加したとき、既存の機能が壊れていないことを自動で確認できる
  2. 仕様の文書化 — テストコード自体が「この機能はこう動くべき」という仕様書の役割を果たす
  3. リファクタリングの安全網 — テストが通る限り、安心してコードの構造を変えられる(→ 第25章

テストがない状態でコードを変更するのは、安全ネットなしで綱渡りをするのと同じです。


📊 テストの3つの層

単体テスト(Unit Test)

モデルのバリデーションやメソッドなど、 1つの部品 をテストします。

# spec/models/article_spec.rb
RSpec.describe Article, type: :model do
  describe "バリデーション" do
    it "タイトルと本文があれば有効" do
      article = build(:article)   # FactoryBotで作成
      expect(article).to be_valid
    end

    it "タイトルがなければ無効" do
      article = build(:article, title: "")
      expect(article).not_to be_valid
      expect(article.errors[:title]).to include("を入力してください")
    end

    it "タイトルが100文字を超えると無効" do
      article = build(:article, title: "a" * 101)
      expect(article).not_to be_valid
    end
  end

  describe "#liked_by?" do
    it "いいね済みならtrueを返す" do
      article = create(:article)
      user = create(:user)
      create(:like, user: user, likeable: article)

      expect(article.liked_by?(user)).to be true
    end
  end
end

統合テスト(Request Spec / Integration Test)

コントローラの リクエスト〜レスポンス をテストします。

# spec/requests/articles_spec.rb
RSpec.describe "Articles", type: :request do
  describe "GET /articles" do
    it "公開記事の一覧が表示される" do
      create(:article, status: :published, title: "Ruby入門")

      get articles_path
      expect(response).to have_http_status(:success)
      expect(response.body).to include("Ruby入門")
    end
  end

  describe "POST /articles" do
    context "ログイン済みの場合" do
      it "記事が作成される" do
        user = create(:user)
        sign_in(user)   # spec/support/auth_helpers.rb に定義したカスタムヘルパー

        expect {
          post articles_path, params: { article: { title: "新記事", body: "本文テスト" } }
        }.to change(Article, :count).by(1)

        expect(response).to redirect_to(article_path(Article.last))
      end
    end

    context "未ログインの場合" do
      it "ログインページにリダイレクトされる" do
        post articles_path, params: { article: { title: "新記事", body: "本文テスト" } }
        expect(response).to redirect_to(new_session_path)
      end
    end
  end
end

E2Eテスト(System Spec)

実際のブラウザ操作 をシミュレートするテストです。

# spec/system/articles_spec.rb
RSpec.describe "記事の投稿", type: :system do
  it "ログインして記事を投稿できる" do
    user = create(:user)

    visit new_session_path
    fill_in "メールアドレス", with: user.email_address
    fill_in "パスワード", with: "password"
    click_button "ログイン"

    visit new_article_path
    fill_in "タイトル", with: "RSpecの使い方"
    fill_in "本文", with: "RSpecはRubyのテストフレームワークです。"
    click_button "投稿する"

    expect(page).to have_content("記事を投稿しました")
    expect(page).to have_content("RSpecの使い方")
  end
end

テストピラミッド

        /\
       /  \       E2Eテスト(少数。重要なフローだけ)
      /    \
     /──────\
    /        \    統合テスト(中程度。各エンドポイント)
   /          \
  /────────────\
 /              \  単体テスト(大量。モデルのバリデーション・メソッド)
/________________\

下にいくほど:速い、安定、数が多い
上にいくほど:遅い、壊れやすい、数が少ない

なぜこの比率なのかというと、単体テストは外部依存(DB・ブラウザ)が少なく高速に実行でき、テストの失敗原因も特定しやすいためです。E2Eテストはユーザー視点の検証ができる反面、ブラウザの起動が必要で遅く、CSSの変更などでテストが壊れやすいという特徴があります。


🚦 TDD — Red → Green → Refactor

TDD(テスト駆動開発) は、「テストを先に書いてからコードを書く」開発手法です。

① Red(赤)   — まずテストを書く → 当然失敗する(赤)
② Green(緑) — テストが通る最小限のコードを書く(緑)
③ Refactor    — コードをきれいにする(テストが緑のまま)
④ 繰り返す
# ① Red — テストを先に書く
it "記事のステータスがdraftならtrue" do
  article = build(:article, status: :draft)
  expect(article.draft?).to be true    # まだ enum 未定義 → 失敗(Red)
end

# ② Green — 通る最小限のコードを書く
class Article < ApplicationRecord
  enum :status, { draft: 0, published: 1 }   # 追加
end
# → テスト通る(Green)

# ③ Refactor — 必要ならリファクタリング

最初は面倒に感じるが、慣れると「何を実装すべきか」が明確になり、結果的に開発が速くなるケースが多いです。テストが失敗した原因を調べるデバッグ手法については(→ 第23章で詳しく扱います)。


🔧 RSpec と Minitest

Minitest — Railsのデフォルト

# test/models/article_test.rb
class ArticleTest < ActiveSupport::TestCase
  test "タイトルがなければ無効" do
    article = Article.new(body: "本文")
    assert_not article.valid?
    assert_includes article.errors[:title], "を入力してください"
  end
end

RSpec — 実務で最も使われるフレームワーク

# spec/models/article_spec.rb
RSpec.describe Article, type: :model do
  it "タイトルがなければ無効" do
    article = build(:article, title: "")
    expect(article).not_to be_valid
    expect(article.errors[:title]).to include("を入力してください")
  end
end
Minitest RSpec
導入 Railsデフォルト(追加不要) Gemで追加
記法 assert_xxx(アサーション式) expect(xxx).to(自然言語風)
実務採用率 約30〜40% 約55〜70%
学習コスト 低い やや高い(DSLが多い)
表現力 シンプル 豊富(describe/context/it)

KnowledgeNoteでは RSpec をメイン に採用します。


🏭 FactoryBot — テスト用データの工場

FactoryBot は、テスト用のデータを簡単に作るためのGemです。

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Name.name }
    sequence(:email_address) { |n| "user#{n}@example.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end

# spec/factories/articles.rb
FactoryBot.define do
  factory :article do
    title { Faker::Lorem.sentence(word_count: 5) }
    body { Faker::Lorem.paragraphs(number: 3).join("\n\n") }
    status { :published }
    association :user   # userファクトリと自動で紐づく
  end
end
# 使い方
build(:user)             # メモリ上にUserを作る(DBに保存しない)
create(:user)            # DBに保存する
create(:user, name: "田中")  # 属性を上書き
create_list(:article, 5)     # 5件まとめて作る

build と create の使い分け

メソッド DBに保存 速度 使いどころ
build しない 速い バリデーションのテスト
create する 遅い DB参照が必要なテスト

テストの速度を保つために、 DBに保存する必要がなければ build を使う のがベストプラクティスです。

let と let! の違い

RSpecでよく使う letlet! には重要な違いがあります。

# let — 遅延評価(実際に使われるまで実行されない)
let(:user) { create(:user) }

# let! — 即時評価(each の前に必ず実行される)
let!(:article) { create(:article, user: user) }
# 使い分けの例
RSpec.describe Article, type: :model do
  let(:user) { create(:user) }

  # ❌ let だと article が作られるのは it ブロック内で article 変数を参照した時
  #    → この例では expect(Article.count) の中で article 変数を参照していないため
  #       create が実行されず、count の変化を検知できない
  let(:article) { create(:article, user: user) }
  it "記事が存在する" do
    expect(Article.count).to eq(1)   # 失敗!(まだ作られていない)
  end

  # ✅ let! なら it ブロックの前に article が作られる
  let!(:article) { create(:article, user: user) }
  it "記事が存在する" do
    expect(Article.count).to eq(1)   # 成功
  end
end

基本は let(遅延評価)を使い、 テスト実行前にデータが存在している必要がある場合だけ let! を使います。


🛠️ KnowledgeNoteでの具体例

# spec/factories/likes.rb
FactoryBot.define do
  factory :like do
    association :user
    association :likeable, factory: :article  # ポリモーフィック
  end
end
# spec/models/user_spec.rb
RSpec.describe User, type: :model do
  describe "#follow / #unfollow" do
    let(:tanaka) { create(:user) }
    let(:suzuki) { create(:user) }

    it "フォロー/アンフォローできる" do
      tanaka.follow(suzuki)
      expect(tanaka.following?(suzuki)).to be true

      tanaka.unfollow(suzuki)
      expect(tanaka.following?(suzuki)).to be false
    end

    it "自分自身はフォローできない" do
      tanaka.follow(tanaka)
      expect(tanaka.following?(tanaka)).to be false
    end
  end
end

💼 面接で聞かれたら?

Q:テストの種類と使い分けを説明してください。

「テストは大きく3層に分かれます。単体テストはモデルのバリデーションやメソッドなど部品単位のテスト、統合テストはコントローラのリクエスト〜レスポンスを検証するテスト、E2Eテストはブラウザ操作をシミュレートするテストです。テストピラミッドの考え方に基づき、単体テストを多く・E2Eテストを少なく書くのが効率的です。単体テストは速くて安定しており、E2Eテストは遅いがユーザー視点の検証ができます。」

深掘りされたら:

  • 「TDDとは?」→ テストを先に書いてからコードを実装する手法。Red(テスト失敗)→ Green(最小限の実装で通す)→ Refactor(きれいにする)のサイクルを繰り返す。
  • 「FactoryBotのbuildとcreateの違いは?」→ buildはメモリ上のみ(DB保存なし)で高速。createはDBに保存する。バリデーションテストにはbuild、関連データのテストにはcreateを使い分ける。
  • 「letとlet!の違いは?」→ letは遅延評価で、テスト内で参照されるまで実行されない。let!は即時評価で、itブロックの前に必ず実行される。テスト前にデータが必要な場面ではlet!を使う。

🔗 もっと深く知りたい人へ(1次情報リンク)


まとめ

  • ✅ テストは「自動車検査」。部品検査(単体)→ 組み立て検査(統合)→ 試運転(E2E)の3層
  • ✅ テストを書く理由はリグレッション防止・仕様の文書化・リファクタリングの安全網の3つ
  • ✅ テストピラミッド:単体テストを多く、E2Eテストを少なく
  • ✅ TDD:テストを先に書く → Red → Green → Refactor のサイクル
  • ✅ RSpecが実務で最も使われる。Minitestはデフォルト
  • ✅ FactoryBotでテストデータを効率的に作る。build / create / let / let! を使い分ける

📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?