Rspecの始め方

RSpecは、Rubyでテストを書くためのフレームワークです。アプリケーションが正しく動いているか確認するための「自動テスト」を簡単に書けます。

この記事では、RSpecを初めて使う方のために、Todoアプリを例にして導入から簡単なコード例までを解説します。

## 目次
1. [RSpecとは？](#1-rspecとは)
2. [RSpecを導入する](#2-rspecを導入する)
3. [RSpecのディレクトリ構成](#3-rspecのディレクトリ構成)
4. [用語の説明](#4-用語の説明)
5. [モデルのテスト](#5-モデルのテスト)
6. [コントローラのテスト](#6-コントローラのテスト)
7. [システムテスト](#7-システムテスト)
8. [よくあるエラーと解決法](#8-よくあるエラーと解決法)
9. [今後の展望](#9-今後の展望)

## 1. RSpecとは？

RSpecは、Rubyのコードをテストするためのツールです。「期待する動作」がちゃんと実現されているか確認するために使います。

## 2. RSpecを導入する

RSpecを使うには、Gemfileに以下を追加します。

```ruby:Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 5.0.0'
end
```

その後、以下のコマンドを実行します。

```bash:ターミナル
bundle install
rails generate rspec:install
```

これで、`spec/`ディレクトリが作成され、RSpecを使う準備が整います。


## 3. RSpecのディレクトリ構成

RSpecをインストール後のディレクトリ構成例は以下の通りです。

```plaintext:ディレクトリ構成図
spec/
├── models/
│   └── todo_spec.rb       # モデルのテストファイル
├── requests/
│   └── todos_spec.rb      # コントローラ（リクエスト）のテストファイル
├── system/
│   └── todos_spec.rb      # システムテストのファイル
├── rails_helper.rb        # Rails特化の設定ファイル
└── spec_helper.rb         # RSpec全体の設定ファイル
```

## 4. テストに使用する用語の説明

| 用語         | 説明 |
|--------------|------|
| **describe** | テスト対象をグループ化します。クラスやメソッド名を指定することが多いです。 |
| **it**       | 個々のテストケースを表します。「何をテストしているか」を具体的に書きます。 |
| **expect**   | 実際の結果が、期待する結果と一致するかを確認します。 |


## 5. モデルのテスト

### 検証内容
- **タイトルが存在する場合に有効であるか**を検証します。
- **タイトルが空の場合に無効であるか**を検証します。

`spec/models/todo_spec.rb` のコード例:

```ruby:spec/models/todo_spec.rb
RSpec.describe Todo, type: :model do
  describe 'バリデーション' do
    it 'タイトルがある場合は有効である' do
      todo = Todo.new(title: '買い物をする')
      puts "検証対象: #{todo.inspect}"
      expect(todo).to be_valid
    end

    it 'タイトルが空の場合は無効である' do
      todo = Todo.new(title: nil)
      puts "検証対象: #{todo.inspect}"
      expect(todo).not_to be_valid
    end
  end
end
```


#### **失敗時のログ**
```ruby:ターミナル
検証対象: #<Todo id: nil, title: nil, completed: nil>
F

Failures:

  1) Todo バリデーション タイトルがある場合は有効である
     Failure/Error: expect(todo).to be_valid
     
       expected #<Todo id: nil, title: nil, completed: nil> to be valid, but got errors: Title can't be blank
```


#### 修正内容
```diff_ruby:ターミナル
# 現状: モデルにバリデーションが存在しない
class Todo < ApplicationRecord
+  validates :title, presence: true
end
```

## 6. コントローラのテスト

### 検証内容
- **GETリクエストで正しいテンプレートがレンダリングされるか**を検証します。

`spec/requests/todos_spec.rb` のコード例:

```ruby:spec/requests/todos_spec.rb
RSpec.describe TodosController, type: :request do
  describe 'GET /todos' do
    it '正しいテンプレートがレンダリングされる' do
      puts "リクエスト: GET /todos"
      get todos_path
      expect(response).to render_template(:index)
    end
  end
end
```

### 失敗時のログ例

#### **失敗時のログ**
```bash:ターミナル
リクエスト: GET /todos
F

Failures:

  1) TodosController GET /todos 正しいテンプレートがレンダリングされる
     Failure/Error: expect(response).to render_template(:index)
       expecting <"index"> but rendering with <"errors/404">
```

### 修正箇所

#### 修正内容
```diff_ruby:todo_controller.rb
# 現状: コントローラーのindexアクションが未実装
class TodosController < ApplicationController
  def index
+   @todos = Todo.all
  end
end
```
```diff_ruby:config/routes.rb
# 現状: ルーティングが存在しない
+ resources :todos, only: [:index]
```

## 7. システムテスト

### 検証内容
- **新しいTodoを作成し、画面に表示されるか**を検証します。

`spec/system/todos_spec.rb` のコード例:

```ruby:spec/system/todos_spec.rb
require 'rails_helper'

RSpec.describe 'Todo管理機能', type: :system do
  it 'Todoを作成し、表示する' do
    puts "画面操作: 新しいTodoを作成"
    visit new_todo_path
    fill_in 'タイトル', with: '新しいTodo'
    click_button '作成'
    expect(page).to have_content('新しいTodo')
  end
end
```

### 失敗時のログ例

#### **失敗時のログ**
```bash:ターミナル
画面操作: 新しいTodoを作成
F

Failures:

  1) Todo管理機能 Todoを作成し、表示する
     Failure/Error: expect(page).to have_content('新しいTodo')
       expected to find text "新しいTodo" in "タスク一覧"
```

### 修正箇所

#### 修正内容
```diff_ruby:todo_controller.rb
# 現状: createアクションがリダイレクトされていない
def create
  @todo = Todo.new(todo_params)
  if @todo.save
+   redirect_to todos_path, notice: 'Todoを作成しました'
  else
    render :new
  end
end
```

---

## 8. よくあるエラーと解決法

### エラー例: `uninitialized constant Todo`
- **原因**: モデルが正しくロードされていません。
- **解決**: モデルの名前やファイルパスを確認しましょう。

### エラー例: `expected: true, got: false`
- **原因**: バリデーションが期待通りに動作していない可能性があります。
- **解決**: モデルに適切なバリデーションを追加しましょう。

## 9. 今後の展望

RSpecでは、今回の内容以外にもさまざまなテストを実装できます。次のような機能を追加した際には、それに対応するテストを記述してみましょう。

#### **1. APIリクエストのテスト**
- RESTfulなAPIエンドポイントに対するリクエストの挙動を確認する。
- **検証内容**:
  - 正しいデータをレスポンスとして返すか。
  - 必要な認証情報がない場合に適切なエラーレスポンスを返すか。
- **例**: `GET /api/todos` が正しいJSONデータを返すかをテスト。

#### **2. 認証機能のテスト**
- ユーザー認証や認可に基づくアクセス制限をテストする。
- **検証内容**:
  - ログインしたユーザーのみがアクセス可能なページで、未ログインの場合にリダイレクトされるか。
  - 管理者のみが特定の機能を利用できるか。

#### **3. パフォーマンステスト**
- 大量のデータやリクエストに対してアプリケーションが期待通りに動作するかを確認する。
- **検証内容**:
  - 1000件のデータを読み込む際の処理時間が一定以内であるか。
  - 高負荷時にエラーが発生しないか。

#### **4. フロントエンドとの統合テスト**
- Vue.jsやReactなどを利用したSPA（シングルページアプリケーション）で、バックエンドとフロントエンドの統合をテストする。
- **検証内容**:
  - APIレスポンスがフロントエンドで正しく描画されるか。
  - ページ遷移やインタラクションが期待通りに動作するか。

#### **5. サードパーティとの連携テスト**
- 外部APIやサードパーティのライブラリとの連携を確認する。
- **検証内容**:
  - サードパーティのAPIに正しいリクエストを送信できているか。
  - レスポンスエラー時に適切なエラー処理が行われるか。

---

次に目指すこと:
- 既存機能のテストカバレッジを拡大。
- モデルやコントローラー以外の部分（ジョブ、メール送信、API）をテスト対象に含める。
- システムテストをCapybaraと連携してユーザー視点の動作確認を行う。

RSpecを使ったテストを書き進めることで、アプリケーション全体の品質が向上します。次回はAPIリクエストや認証機能のテストにも挑戦してみましょう！