【RSpec】単体テストを完全理解する

# この記事を読んでほしい人
- Ruby on railsのテストコードをこれから学ぼうとしている人
- 単体テストコードで躓いた人
- 単体テストコードを勉強したけど、振り返ってより理解したい人

# この記事のゴール
以下を理解することができます。
- Rspecが何をしているのか
- モデルファイルの重要性
- 単体テストコードを書くには何を見れば良いのか

# Rspecとは
Rspecは、Ruby言語用のテストフレームワークです。
プログラムが**期待通りに動作することを確認するため**のテストコードを書くために使用されます。

>Rspecは「ビヘイビア駆動開発」（Behavior-Driven Development, BDD）の原則に基づいており、開発者が人間が読める形式でテストを記述できるように設計されています。これにより、テストコードが仕様書のように機能し、プログラムの振る舞いについての明確なドキュメントを提供します。

# Rspec何をするのか
モデルの単体テストは、モデルに書いたバリデーションが正しく働いているかを確認します。

### バリデーションの復習
バリデーションとは、
モデルファイルに記述する、データが正しい形式や条件を満たしていることを確認するためのルールや制約のことです。

例えば、deviseを用いてユーザーのモデルを作成すると、以下のような記述が自動で導入されます。
```app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
```
ここで、上記の`:validatable`を深掘りましょう。
実はこの`:validatable`には、以下のバリデーションがデフォルトで備わっています。
| バリデーション | エラーメッセージ |
|:-----------|------------:|
| emailが必須       | Email can't be blank        |
| emailの一意性(重複してはいけない)     | Email has already been taken      |
|emailの形式(@ない)   | Email is invalid    |
| パスワードが必須       | Password can't be blank        |
| パスワードの長さ(6-128文字)         | Password is too short (minimum is 6 characters) / Password is too long (maximum is 128 characters)         |
| パスワードと確認用のパスワードが一致しない       | Password confirmation doesn't match Password       |

上記のようなバリデーションがすでに備わっているため、例えばメールアドレスが`ABC.example.com`(@がない、大文字が使われてる)だと、`:validatable`はこれを無効なメールアドレスとして識別して、以下のようにエラーが表示されるのです。

![スクリーンショット 2024-04-03 8.33.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2737070/062613c2-a832-ea0c-e072-e4a01a6c575c.png)

従って、deviseを使用している場合は、「メールアドレスが空欄なとき、`@`がないとき」というようなテストを行う必要があります。

また、他にもよく使う例として、以下のようなバリデーションがあります。
```app/models/user.rb
validates :nickname,      presence: true
```
上記は「ニックネームが必須」というバリデーションになります。
| バリデーション | エラーメッセージ |
|:-----------|------------:|
| presence: true       | can't be blank       |

`presence: true`を使用した場合は、テストを実行した際に「can't be blank」というエラーメッセージが返ってくるようになっています。
(これがテストを実行した時に「can't be blank」と返ってくる正体)

# なぜモデルファイルのバリデーションと関係するのか？

### そもそもテストをする意味
テストをする意味は、**不正なデータだとエラーを返してくれるか確かめるため**です。

### 手動でテストをするデメリット
手動でテスト(挙動確認)をすると、以下のようなデメリットが生じます。
- テスト漏れなどの人為的なミスが起きる
- 仕様が変わった時に、再度テストしなければいけない
- どの項目をテストしたのか記録が残らない

### じゃあ何を見てテストコードを書けばいいの？
「不正なデータの場合はエラーを返してくれるか」なので、バリデーションがちゃんと機能するかをテストすれば良いのです。

だからテストコードを書くときは、**バリデーションを見てテストすれば良い**のです。


# 単体テストコードは何を書くのか
以下はユーザーモデルの単体テストを書いた例です。

```spec/models/user_spec.rb
require 'rails_helper' #これは定型

RSpec.describe User, type: :model do #ユーザーモデルテスト
  before do
    #factorybotで指定した正常データが入っている
    #各テストでユーザーデータを使用するため「インスタンス変数(@)」にする
    @user = FactoryBot.build(:user)
  end
  
  describe 'ユーザー新規登録' do
    context '新規登録がうまくいくとき' do #正常系
      it "正常データが存在すれば登録できる" do
        expect(@user).to be_valid
      end  #itのend
    end  #contextのend

    context '新規登録がうまくいかないとき' do #異常系
      it "nicknameが空だと登録できない" do
        @user.nickname = ""  # nicknameの値を空にする
        @user.valid? #バリデーションを確認する
        
        # binding.pry エラーメッセージを確認したかったらデバックする
        
        expect(@user.errors.full_messages).to include("Nickname can't be blank") #ここはセット
      end
    end

  end  #describeのend
```

それぞれのキーワードについて解説します。
| キーワード  | 説明 |
|-----------|----------------------------------------------------------------|
| describe | 何についてテストするのかを示します。例えば、「ログイン機能」とか「プロフィール更新」といったアプリの特定の部分や機能です。 |
| context  | 特定の条件を示します。テストケースのグループを「正常系」と「異常系」などの条件で区別する時によく使われます。 |
| it       | 個々のテストケースについて説明します。ここでは、「ユーザーがメールとパスワードでログインできる」といった具体的な動作や結果について書きます。 |

これらが分かれば、まるで雛形のように単体テストを見ることができます。

しかし**さらに雛形のように**単体テストを読むために、異常系を見てみましょう。

```spec/models/user_spec.rb
context '新規登録がうまくいかないとき' do #異常系
      it "nicknameが空だと登録できない" do
        @user.nickname = ""  # nicknameの値を空にする
        @user.valid? #バリデーションを確認する
        
        # binding.pry エラーメッセージを確認したかったらデバックする
        
        expect(@user.errors.full_messages).to include("Nickname can't be blank") #ここはセット
      end
    end
```
異常系のテストでは、以下のように**パターン**があります。
| 順番 | コード                                              | 説明                                                   |
|----|-----------------------------------------------------|--------------------------------------------------------|
| ①  | `it "nicknameが空だと登録できない" do`               | テストの説明またはタイトル                             |
| ②  | `@user.nickname = ""`                               | 異常系データを作成（nicknameを空に設定） |
| ③  | `@user.valid?`                                      | ユーザーモデルのバリデーションを通過するか確認 |
| ④  | `expect(@user.errors.full_messages).to include("Nickname can't be blank")` | 返されたエラーメッセージが期待通りか確認               |
| ⑤  | `end`                                               | ①の do に対する終了ブロック                            |

大体は上記のパターンで構成されているだけです。

### 補足
```spec/models/user_spec.rb
expect(@user.errors.full_messages).to include("Nickname can't be blank") 
```
上記コードを分解すると
| キーワード        | 説明 |
|----------------|-------------------------------------------------------------------|
| `full_messages` | バリデーションエラーの完全なメッセージを配列として取得します。フィールド名(今回の場合は'Nickname')とエラーメッセージ(今回の場合は'can't be blank)を組み合わせた文を返します。 |
| `include`      | 期待する文字列が配列内に存在するかどうかをチェックします。この場合、特定のエラーメッセージが含まれているかを検証します。 |

というふうに説明できますが、「`expect`から`can't be blank`まで、まるッとセット」と覚えてただいて、一旦は構いません。

### 補足&振り返り(can't be blankはどこから来てる？)
前述でモデルファイルのバリデーションからだと解説しました。
>```app/models/user.rb
>validates :nickname,      presence: true
>```
>上記は「ニックネームが必須」というバリデーションになります。
>| バリデーション | エラーメッセージ |
>|:-----------|------------:|
>| presence: true       | can't be blank       |

しかし、これだと「can't be blank」以外が分かりません。
そこでテストコード内に`binding.pry`を用いることで、エラーメッセージを知ることができます。
```app/models/user.rb
describe 'ユーザー新規登録' do
   it "nicknameが空だと登録できない" do
   　user = User.new(nickname: "", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000")
     user.valid?
     binding.pry       # ここで止める
   end
   ```
そしてターミナル上で、テストを実行します。
```terminal
% bundle exec rspec spec/models/user_spec.rb 

[1] pry(main)> user.errors　# 入力する
=> #<ActiveModel::Errors:0x00007fca8d0c5cd8
@base=#<User id: nil, email: "kkk@gmail.com", created_at: nil, updated_at: nil, nickname: "">,
@details={:nickname=>[{:error=>:blank}]},
@messages={:nickname=>["can't be blank"]}>

[2] pry(main)>  user.errors.full_messages # 入力する
=> ["Nickname can't be blank"]　 # エラー文が分かる
```

このように、エラーメッセージを知る方法もあります。
(このやり方のほうが正式)

# 最後に
以上でテストコードについて理解してきました。
テストコードの学習はビューファイルに変化があるわけでなく、同じようなコードをずっと書いて「つまらない」と感じるかもしれませんが、webエンジニアになるためには**絶対に避けて通れない道**です。
アプリをリリースできるのも、テストがあってこそです。
構造、パターンを掴めばカンタンなので、学習頑張っていきましょう。