モデルに追加したメソッドをテストしよう

学習の目標

本章では、以下の内容を学習します。

  • モデルに独自のメソッドを追加する方法とその利点を理解する
  • コードの動作確認における手動テストとユニットテストの使い分けを学ぶ
  • RSpecにおけるcontextブロックを使用した条件分岐のテスト手法を習得する
  • モデルのビジネスロジックを適切にテストするための考え方を理解する
  • データベースの関連付けを活用したメソッドのテスト方法を学ぶ

はじめに

これまでの章では、モデルの基本的な機能であるバリデーションやアソシエーションについて学んできました。しかし、実際のRailsアプリケーション開発では、モデルは単なるデータベースとのインターフェース以上の役割を果たします。

モデルに独自のメソッドを追加することで、アプリケーション固有のビジネスロジックを実装できます。たとえば、記事の読了時間を計算したり、ユーザーの年齢を誕生日から算出したりといった処理です。このような処理をモデルに集約することで、コードの再利用性が高まり、保守性も向上します。

今回は、記事に紐づくコメントの数を取得するメソッドを実装し、それをテストする方法を学んでいきます。実際の開発でもよく使われるパターンなので、しっかりと身につけていきましょう。

オリジナルメソッドの実装

それでは、Articleモデルにコメント数を返すメソッドを追加してみましょう。

app/models/article.rbを開いて、以下のようにメソッドを追加してください。

class Article < ApplicationRecord
validates :title, presence: true
validates :content, presence: true, length: { minimum: 10 }
has_many :comments, dependent: :destroy
# コメントの数を返すメソッド
def comments_count
comments.count
end
end

新しく追加したcomments_countメソッドについて説明します。

このメソッドは、記事に紐づくコメントの数を返します。実装はとてもシンプルで、comments.countを呼び出すだけです。

ここでcommentsは、前章で設定したhas_many :commentsによって使えるようになったメソッドです。そしてcountメソッドは、SQLのCOUNTクエリを発行して、データベースレベルでレコード数を集計します。

この実装方法の優れている点は、コメントが1000件あっても10000件あっても、すべてのコメントをメモリに読み込むことなく、データベースで集計した結果だけを返すことです。そのため、メモリ効率が良く、高速に動作します。

手動での動作確認

実装したメソッドが正しく動作するか、まずはRailsコンソールで確認してみましょう。手動での確認は、実装の初期段階で素早く動作を検証するのに便利です。

bundle exec rails c

コンソールが起動したら、以下のコマンドを順番に実行してみてください。

# 記事の作成
article = Article.create(title: 'テスト記事', content: 'これはテスト用の記事です。')
# コメントの作成
article.comments.create(content: '1つ目のコメント')
article.comments.create(content: '2つ目のコメント')
# メソッドの動作確認
article.comments_count
# => 2

期待通り、2という値が返ってきました。コメントを2つ作成したので、正しく動作していることが確認できます。

確認が終わったら、exitでコンソールを終了してください。

ユニットテストの作成

手動での確認ができたら、次は自動テストを作成します。手動テストは素早く確認できる反面、毎回同じ操作を繰り返す必要があり、見落としも発生しやすいという欠点があります。

自動テストを書くことで、メソッドの仕様を明確にし、将来コードを変更した際にも正しく動作することを保証できます。

spec/models/article_spec.rbを開いて、既存のテストの後に以下のテストを追加してください。

require 'rails_helper'
RSpec.describe Article, type: :model do
let(:title) { 'RSpecの基本' }
let(:content) { 'RSpecを学びましょう。楽しいですよ!' }
# 既存のテストは省略
describe '#comments_count' do
before do
@article = Article.create(title: title, content: content)
end
context '紐づくコメントがある場合' do
it 'コメントの数を正しく返す' do
@article.comments.create(content: '素晴らしい記事です')
@article.comments.create(content: 'とても参考になりました')
expect(@article.comments_count).to eq(2)
end
end
context '紐づくコメントがない場合' do
it '0を返す' do
expect(@article.comments_count).to eq(0)
end
end
end
end

このテストコードの構造を詳しく見ていきましょう。

describeでメソッドをグループ化

describe '#comments_count'という記述で、comments_countメソッドに関するテストをグループ化しています。#を付けることで、これがインスタンスメソッドのテストであることを示しています。

beforeブロックでの準備

各テストケースの実行前に、@articleというインスタンス変数に記事を保存しています。これにより、各テストで共通して使用する記事を一度だけ作成すれば済みます。

contextによる条件分岐

contextブロックを使って、「コメントがある場合」と「コメントがない場合」という2つの状況を明確に分けています。これにより、どのような条件でテストしているのかが一目でわかります。

エッジケースのテスト

コメントが存在しない場合のテストも含めています。このようなエッジケース(境界条件)のテストは、メソッドの堅牢性を確保する上で重要です。実際の開発でも、「データが存在しない場合」の処理は見落としがちなので、テストでカバーしておくことが大切です。

テストの実行と検証

作成したテストを実行して、メソッドが期待通りに動作することを確認しましょう。

bundle exec rspec spec/models/article_spec.rb

以下のような結果が表示されれば成功です。

Article
#comments_count
紐づくコメントがある場合
コメントの数を正しく返す
紐づくコメントがない場合
0を返す
Finished in 0.07446 seconds (files took 2.22 seconds to load)
2 examples, 0 failures

両方のテストケースが通りました。これで、実装したメソッドが正しく動作することが自動テストで保証されました。

ビジネスロジックをモデルに集約する利点

今回実装したcomments_countメソッドは、とてもシンプルな例でした。しかし、このようにモデルにメソッドを追加することには、いくつかの利点があります。

まず、コードの再利用性が高まります。コメント数を表示したい場所が複数ある場合でも、article.comments_countと書くだけで済みます。

また、ビジネスロジックが一箇所に集約されるため、仕様変更にも対応しやすくなります。たとえば、「削除されたコメントは数に含めない」という仕様が追加された場合でも、comments_countメソッドを修正するだけで、アプリケーション全体に変更が反映されます。

さらに、テストも書きやすくなります。モデルのメソッドは独立してテストできるため、複雑なビジネスロジックでも確実に動作を検証できます。

まとめ

本章では、モデルに独自のメソッドを追加し、それをテストする方法を学びました。以下の内容を理解できたことと思います。

  • モデルにビジネスロジックを実装することの利点
  • 手動テストと自動テストそれぞれの役割と使い分け
  • describeを使ったメソッド単位でのテストの構造化
  • contextを使った条件別のテストケースの整理
  • エッジケースを含めた網羅的なテストの重要性

モデルに適切なメソッドを追加することで、アプリケーションの設計がより良いものになります。そして、それらのメソッドに対して適切なテストを書くことで、安心して機能を追加・変更できるようになります。

これで、Railsのモデルテストについての基本的な内容は一通り学びました。次の章では、コントローラーのテストについて学んでいきます。

このセクションは有料サブスクリプションへの登録、またはログインが必要です。完全なコンテンツにアクセスするには、料金ページ(/pricing)をご覧ください。購入済みの場合は、ログインしてください。

Basicプランでより詳しく学習

この先のコンテンツを読むにはBasicプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。

作成者:とまだ
Previous
モデル間のアソシエーションをテストしよう