2
1

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

Posted at

この記事を読んでほしい人

  • 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

従って、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

そしてターミナル上で、テストを実行します。

% 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エンジニアになるためには絶対に避けて通れない道です。
アプリをリリースできるのも、テストがあってこそです。
構造、パターンを掴めばカンタンなので、学習頑張っていきましょう。

2
1
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
2
1