💯

【Rails】RSpecとは - ユーザーモデルの単体テストをやってみる -

2023/10/01に公開

RSpecとは

Ruby向けのテストフレームワークで、プログラムが正しく動作しているかを確認するツールです。

単体テストと結合テスト

主要なテストに以下の2つがあります。

単体テスト(Unit Testing)

クラスやメソッドが正しく動作するかテストします。
例:

  • タイトルの文字数制限を超えたときにエラーがでるか
  • 正しいユーザー名とパスワードでログインできるか

統合テスト(Integration Testing)

複数の単体テストの組み合わせをテストします。
例:

  • カテゴリー一覧から特定のカテゴリーページに遷移できるか
  • ユーザーがログインし、記事を投稿できるか

テストをする理由

  1. バグの早期発見
    コードを自動的にテストし、バグの早期発見をすることで、本番環境に影響を与える前に修正できます。

  2. コードの可読性向上
    テストコードはコード全体の理解に役立つドキュメンテーションとしての役割にもなります。テストはコードがどのように動作するかを具体的に示すものなので、コードの利用方法や動きが文書化されたものとして利用できます。

  3. 品質管理
    テストを通じて客観的なデータとして品質を担保できるため、クライアントやユーザーからの信頼を築くことができます。

RSpecの実装

RSpecの実装方法について説明します。

gemのインストール

以下を追記し、bundle installします。

Gemfile
:
 group :test do
   # Adds support for Capybara system testing and selenium driver
   gem 'capybara', '>= 3.26'
   gem 'selenium-webdriver', '>= 4.0.0.rc1'
   # Easy installation and use of web drivers to run system tests with browsers
   gem 'webdrivers'
 + gem 'rspec-rails'
 + gem 'factory_bot_rails'
 + gem 'faker'
 end
:
  • gem 'rspec-rails'
    RSpecを導入します。

  • gem 'factory_bot_rails'
    Factory Botはテストデータの生成を助けるためのライブラリで、テスト時にデータを簡単に作成できます。

  • gem 'faker'
    Fakerはダミーデータを生成するためのツールで、テストデータやサンプルデータを作成するのに便利です。

RSpecを使用のための設定

  1. ターミナルからRSpecのインストール
ターミナル
rails g rspec:install
  1. specフォルダの配下にfactoriesフォルダ作成
  2. FactoryBotの設定
    FactoryBotが使えるようspec/rails_helper.rbの最後のend前に以下の記述をします。
    spec/fails_helper.rb
      config.include FactoryBot::Syntax::Methods
    end
    

FactoryBotにテストデータの定義を作成

アプリケーションに合わせて必要なテストデータを作成します。
例えばuserモデルのテストデータを作ってみます。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname { Faker::Lorem.characters(number: 10) }
    account { Faker::Lorem.characters(number: 10) }
    email { Faker::Internet.email }
    password { "password" }
    password_confirmation { "password" }

    # 正しいYouTubeチャンネルのURL形式
    channel { "https://www.youtube.com/channel/CHANNEL_ID" }

    factory :user_with_invalid_channel do
      # 正しくないYouTubeチャンネルのURL形式
      channel { "https://example.com" }
    end
  end
end

モデルの単体テスト

userモデルのテストをやってみます。

  1. specフォルダの配下にmodelsというフォルダを作成

  2. 作成したmodelsフォルダの配下にuser_spec.rbというファイルを作成

  3. 作成したファイルに以下を記述します。

    spec/models/user_spec.rb
    # frozen_string_literal: true
    require 'rails_helper'
    
    • # frozen_string_literal: true
      文字列の変更、バグを防ぐため。
    • require 'rails_helper'
      RSpecの設定を読み込むため。
  4. テストコード記述

spec/models/user_spec.rb
# frozen_string_literal: true
require 'rails_helper'

RSpec.describe User, type: :model do
  subject { user.valid? }

  let(:user) { build(:user) } # FactoryBotを使用してUserインスタンスを作成

  describe 'validations' do
    context 'nicknameカラム' do
      it '空欄でないこと' do
        user.nickname = ''
        is_expected.to eq false
      end

      it '2文字以上であること: 1文字は×' do
        user.nickname = Faker::Lorem.characters(number: 1)
        is_expected.to eq false
      end

      it '2文字以上であること: 2文字は〇' do
        user.nickname = Faker::Lorem.characters(number: 2)
        is_expected.to eq true
      end

      it '20文字以下であること: 21文字は×' do
        user.nickname = Faker::Lorem.characters(number: 21)
        is_expected.to eq false
      end

      it '20文字以下であること: 20文字は〇' do
        user.nickname = Faker::Lorem.characters(number: 20)
        is_expected.to eq true
      end
    end

    context 'accountカラム' do
      it '空欄でないこと' do
        user.account = ''
        is_expected.to eq false
      end

      it '3文字以上であること: 2文字は×' do
        user.account = Faker::Lorem.characters(number: 2)
        is_expected.to eq false
      end

      it '3文字以上であること: 3文字は〇' do
        user.account = Faker::Lorem.characters(number: 3)
        is_expected.to eq true
      end

      it '20文字以下であること: 21文字は×' do
        user.account = Faker::Lorem.characters(number: 21)
        is_expected.to eq false
      end

      it '20文字以下であること: 20文字は〇' do
        user.account = Faker::Lorem.characters(number: 20)
        is_expected.to eq true
      end

      it '正しいフォーマットであること: 数字とアルファベット、ハイフン、アンダースコアが許可される' do
        valid_accounts = ['user123', 'user-name', 'user_name']
        invalid_accounts = ['user@123', 'user.name', 'user$name']

        valid_accounts.each do |account|
          user.account = account
          expect(user).to be_valid
        end

        invalid_accounts.each do |account|
          user.account = account
          expect(user).not_to be_valid
        end
      end
    end


    context 'emailカラム' do
      it '空欄でないこと' do
        user.email = ''
        is_expected.to eq false
      end

      it '有効なメールアドレスの形式であること' do
        valid_emails = ['user@example.com', 'test.user@gmail.com', 'test_user123@example.co.jp']
        invalid_emails = ['invalid_email', 'user@.com', 'user@example']

        valid_emails.each do |email|
          user.email = email
          expect(user).to be_valid
        end

        invalid_emails.each do |email|
          user.email = email
          expect(user).not_to be_valid
        end
      end
    end

    context 'channelカラム' do
      it '正しいYouTubeチャンネルのURL形式であること' do
        user.channel = 'https://www.youtube.com/channel/CHANNEL_ID'
        is_expected.to eq true
      end

      it '正しくないYouTubeチャンネルのURL形式であること' do
        user.channel = 'https://example.com'
        is_expected.to eq false
      end
    end

    context 'active_for_authentication?' do
      it 'activeステータスのユーザーはログインできること' do
        user.status = 'active'
        expect(user.active_for_authentication?).to eq true
      end

      it 'bannedステータスのユーザーはログインできないこと' do
        user.status = 'banned'
        expect(user.active_for_authentication?).to eq false
      end

      it 'inactiveステータスのユーザーはログインできないこと' do
        user.status = 'inactive'
        expect(user.active_for_authentication?).to eq false
      end
    end

  end
end

今回テストをかけたモデルの抜粋は次の通りです。

class User < ApplicationRecord
:
  validates :account, presence: true, format: { with: /\A[a-z0-9_-]+\z/ }, length: { minimum: 3, maximum: 20 }
  validates :nickname, presence: true, length: {minimum: 2, maximum: 20 }
  validates :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i, message: "は有効なメールアドレスの形式である必要があります" }
  validates :channel, format: { with: /\A\z|https?:\/\/(?:www\.)?youtube\.com\/channel\/[\w-]+\z/, message: "は正しいYouTubeチャンネルのURL形式ではありません" }
  
  # 会員ステータス
  enum status: { active: 0, banned: 1, inactive: 2 }  

  # active会員のみログインできる
  def active_for_authentication?
    super && (status == 'active')
  end
:
end

テストの実行

ターミナルでテストしたいファイル名を実行します。
今回は以下の通り。

ターミナル
rspec spec/models/user_spec.rb

エラーがないと次のようになります!


テストコードを書くのって色んなケースや条件を考えないといけないのでとても勉強になりました。
これは慣れだな~
userのテストは汎用性があるかと思い今回記事にしてみました。
参考になったら嬉しいです。
最後まで読んでいただきありがとうございました。

Discussion