【Ruby on Rails】 RSpec モデルの単体テスト記述方法

こんにちは、株式会社Pentagonでアプリ開発をしている中原です。

Railsのテストを書く方法について調査することがあり、どのようにテストを書いて良いか、分からなかったためその方法を記事にしました。本記事では、テストフレームワークRSpecを使用したモデルの単体テストの書き方について説明します。

【こんな人に読んで欲しい】

  • Railsエンジニア
  • テストを書く方法が分からない方

【この記事を読むメリット】

  • テストを書く方法について学ぶことができる。
  • RSpecを使用した単体テストを書く方法について学ぶことができる。

【結論】
テストを書く方法はgiven、 when、 thenのフレームワーク通りにテストコードを書くことです。
前回の記事から環境構築を行い、本記事を読み、実装を行えば、RSpecを使用したモデルの単体テストを書くことができます。RSpecを使用することで、テストコードの可読性を向上させ、より簡単にテストを書くことができます。

目次

前提条件

前回の記事から環境構築を行なってください。

また以下の文献を参考にRidgepoleの導入を行なってください。

  • Ridgepoleの導入

参考
【Rails】migrationからRidgepoleに移行する
https://zenn.dev/nyazuki/articles/28d1d4e4ebef05

RSpecとは

RSpecは、RubyベースのBDD(行動駆動開発)テスティングフレームワークです。BDDとは、開発者がアプリケーションの振る舞いを記述し、それに対するテストケースを作成することで、アプリケーションの品質を向上させる手法です。RSpecは、自然言語に近い構文を使用してテストケースを記述するため、テストコードの可読性を向上させ、開発者がテストコードを簡単に理解することができます。またテストケースの実行結果をグラフィカルに表示できるため、テストケースの実行結果を直感的に理解することができます。

テストに必要なGemを導入

group :development, :test do
  # テストフレームワーク
  gem 'rspec-rails'
  # テスト用オブジェクトの生成
  gem 'factory_bot_rails'
  # ダミーデータの作成
  gem "faker"

  ######### 省略 #########
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end

追加したGemをインストール

docker-compose exec web bundle install

RSpecのセットアップ

docker-compose exec web rails g rspec:install

以下のファイルが作成されます。

   identical  .rspec
      create  spec # specの設定ファイル
      create  spec/spec_helper.rb # Railsの設定ファイル
      create  spec/rails_helper.rb # RSpecの全体的な設定ファイル

.rspeファイルの編集

--require spec_helper
--color # 追加
--format documentation # 追加

テスト実行時の出力がドキュメント形式や色付けされ可読性が高まります。

FactoryBotの設定

require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'

######### 省略 #########

# コメントアウトを外す
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

######### 省略 #########

RSpec.configure do |config|

######### 省略 #########

  config.include FactoryBot::Syntax::Methods # 追加
end

テスト用オブジェクトを生成時にFactoryBotを省略してcreate()build()と書くことができます。

FactoryBot.build(title: '', content: '')
↓
build(title: '', content: '')

自動生成ファイル設定

module App
  class Application < Rails::Application
    config.load_defaults 7.0
    config.api_only = true

    ######## 省略 ########

    # ここから
    config.generators do |g|
      g.test_framework :rspec, # テストフレームワークとしてRSpecを指定
      request_specs: false, # リクエストスペックを作成しない
      fixtures: false, # テストデータを作るfixtureを作成しない
      view_specs: false, # ビュー用のスペックを作成しない
      helper_specs: false, # ヘルパー用のスペックを作成しない
      routing_specs: false # ルーティングのスペックを作成しない
    end
    # ここまでを追加
  end
end

Railsのジェネレータで生成するファイルを制限しています。
ジェネレータを使用してmodelcontrollerのファイルを生成すると、テスト用のファイルも生成します。

以上でRSpecの設定は完了です。

given, when, thenのフレームワークとは

given、 when、 thenのフレームワークはテストをより構造化し、可読性を高め、テストケースの意図を明確にするために非常に役立つ記法です。 各セクションに分けてテストを記述することで、テストが何をしているのか、どのような条件で実行されるのか、期待される結果は何か、が明確になります。 これにより、テストケースの理解やメンテナンスが容易になります。

given

  • テストを実行するための前提条件を定義します。
  • データを作成します。

when

  • テストケースの条件に応じて、givenで定義したデータを変化させます。
  • 変化させたデータを用いて、テスト対象のメソッドを呼び出し、テストを実行します。

then

  • メソッドの呼び出しの結果が、期待される結果と一致するかを検証します。

Postモデルの単体テスト

DBスキーマの定義

options = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED"

create_table "posts", force: :cascade, options: options do |t|
  t.string "title" # 投稿タイトル
  t.text "content" # 投稿内容
  t.datetime "created_at", null: false # 投稿日時
  t.datetime "updated_at", null: false # 更新日時
end

db/SchemafileにPostテーブルを追加します。

マイグレーション

docker-compose exec web rake ridgepole:apply RAILS_ENV=test

Schemafileの内容をテスト環境にマイグレーションします。

Postモデルの作成

docker-compose exec web rails g model Post --skip-migration

Postモデルを作成します。
以下のファイルが作成されます。

      invoke  active_record
      create    app/models/post.rb # modelファイル
      invoke    rspec
      create      spec/models/post_spec.rb # テスト用のmodelファイル
      invoke      factory_bot
      create        spec/factories/posts.rb # テスト用オブジェクトデータ

Postモデルの定義

class Post < ApplicationRecord
  # 空文字、11文字以上の場合はバリデーションエラー
  validates :title, presence: true, length: { maximum: 11 }
  # 空文字、100文字以上の場合はバリデーションエラー
  validates :content, presence: true, length: { maximum: 100 }
end

titleとcontentのバリデーションを追加します。

テストデータを作成

FactoryBot.define do
  factory :post do
    # title "Cooking"
    title { Faker::Hobby.activity }
    # 英数字のランダムな文字列を生成する(100文字)
    content { Faker::Lorem.characters(number: 100) }
    created_at { DateTime.now }
    updated_at { DateTime.now }
  end
end

Postモデルのテストを行うため、Fakerを使用してテストデータを作成します。
Fakerの使い方はFaker::[ジャンル].[タイトル]と入力すればダミーデータが生成できます。
どんなものが用意されているかは公式から確認してください。

https://github.com/thoughtbot/factory_bot

テストの作成

今回のテストは以下のテストケースで行います。

  1. title、contentのバリデーションが通ること。

  2. titleが空の場合はバリデーションが通るかどうか。

  3. titleが10文字を超える場合はバリデーションが通るかどうか。

  4. contentが空の場合はバリデーションが通るかどうか。

  5. contentが100文字を超える場合はバリデーションが通るかどうか。

require 'rails_helper'

RSpec.describe Post, type: :model do
  # given データの準備
  let(:post) { build(:post) } # モデルのテストデータを準備

  describe "title、contentのバリデーション" do # テストの対象が何かを記述する。
    it "title、contentのバリデーションが通ること" do # 期待値を記述する
      # when 変化
      # then 結果を比較
      expect(post).to be_valid 
    end
  end

  describe "titleのバリデーション" do # テストの対象が何かを記述する。
    it "titleが空の場合はバリデーションする" do # 期待値を記述する
      # when 変化
      post.title = ''
      # then 結果を比較
      expect(post).to_not be_valid
    end
    it "titleが10文字を超える場合はバリデーションする" do
      # when 変化
      post.title = 'a' * 12
      # then 結果を比較
      expect(post).to_not be_valid 
    end
  end

  describe "contentのバリデーション" do # テストの対象が何かを記述する。
    it "contentが空の場合はバリデーションする" do # 期待値を記述する
      # when 変化
      post.content = ''
      # then 結果を比較
      expect(post).to_not be_valid 
    end
    it "contentが100文字を超える場合はバリデーションする" do
      # when 変化
      post.content = 'a' * 101
      # then 結果を比較
      expect(post).to_not be_valid 
    end
  end
end
  • let(:post) : letは遅延評価と呼ばれ、定義時の処理は実行されません。expect(post)のpostが呼ばれたタイミングでPostモデルのインスタンスを生成します。

  • build(:post) : メモリに一時的にインスタンスを生成します。

  • expect(post).to : メソッドの呼び出しの結果が、期待される結果と一致するかを検証します。期待される結果と一致する場合はテストが成功し、不一致な場合はテストが失敗します。

  • expect(post).to_not : メソッドの呼び出しの結果が、期待される結果と一致するかを検証します。期待される結果と不一致な場合はテストが成功し、一致する場合はテストが失敗します。

  • be_valid : バリデーションが通るかどうかを試しています。

  • メソッドの呼び出しの結果が、期待される結果と一致するかを検証します。

テストの実行

docker-compose exec web rspec spec/models/post_spec.rb

テストの結果

テストが成功した場合は、成功した各タイトルが緑色で表示され、テストが失敗した場合は、失敗した各タイトルが赤色で表示されます。この場合は各タイトルが緑色に表示され、5つのテストが正常に実行されたことを示しています。

テスト結果の内容

  1. title、contentのバリデーションが通ること。
    expect(post).toの呼び出しの結果postが、期待される結果be_validと一致したため、テストは成功しました。

  2. titleが空の場合はバリデーションが通るかどうか。
    expect(post).to_notの呼び出しの結果postが、期待される結果be_validと不一致だったため、テストは成功しました。

  3. titleが11文字を超える場合はバリデーションが通るかどうか。
    expect(post).to_notの呼び出しの結果postが、期待される結果be_validと不一致だったため、テストは成功しました。

  4. contentが空の場合はバリデーションが通るかどうか。
    expect(post).to_notの呼び出しの結果postが、期待される結果be_validと不一致だったため、テストは成功しました。

  5. contentが100文字を超える場合はバリデーションが通るかどうか。
    expect(post).to_notの呼び出しの結果postが、期待される結果be_validと不一致だったため、テストは成功しました。

まとめ

RSpecはグラフィカルなテスト結果表示機能により、テストコードを書くことが苦手な方でも、テスト結果を簡単に把握できます。また、RSpecはテストコードを記述するための豊富な機能を提供しており、テストコードの記述を理解しつつ、より簡単に記述することができます。
テストコードを書くことが苦手な方もこれを機会に導入してみてはいかがでしょうか?

採用情報はこちら
目次