changeマッチャーで値の変化を検証しよう
- 学習の目標
- はじめに
- テスト対象クラスの実装
- ポイント加算機能の実装
- changeマッチャーを使用したテストの実装
- ポイント使用機能の実装とテスト
- changeマッチャーの応用
- changeマッチャーのさまざまな使い方
- まとめ
学習の目標
本章では、以下の内容を学習します。
- changeマッチャーの基本的な概念と、値の変化を検証するテストの重要性を理解する
- 実践的なクラス設計とテストの書き方を通じて、オブジェクト指向プログラミングの基本を習得する
- 状態(ポイント残高)を持つクラスの実装とテストについて学ぶ
- RSpecの特徴的な構文(ブロックを使用したマッチャー)の使い方を理解する
- 正常系(値が変化する場合)と異常系(値が変化しない場合)の両方のテストパターンを学ぶ
はじめに
RSpecには様々な便利なマッチャーが用意されていますが、本章では特にchangeマッチャーについて学習していきます。
changeマッチャーとは、ある処理を実行した前後で値がどのように変化したのかを検証するためのものです。実際のアプリケーション開発では、「ポイントが正しく加算されたか」「在庫数が適切に減少したか」といった値の変化を確認する必要が頻繁に発生します。これまで学んだマッチャーとは少し異なる書き方になりますが、changeマッチャーを使うことで、このような値の変化を簡潔かつ明確にテストすることができます。
それでは、実際にコードを書きながらchangeマッチャーの使い方を学んでいきましょう。
テスト対象クラスの実装
まずは、テスト対象となるコードを作成していきます。今回はポイントを管理するためのAccountクラスを新しく実装してみましょう。
account.rbというファイルを作成し、以下のコードを記述してください。
class Account  def initialize(points: 0)    @points = points  end
  def points    @points  endendこのクラスの実装について、詳しく説明していきます。
initializeメソッドは、新しいAccountオブジェクトが作成されるときに呼び出される特別なメソッドです。引数のpoints: 0は、Rubyのキーワード引数という機能を使用しています。これにより、初期ポイントを指定できると同時に、指定がない場合は0がデフォルト値として使用されます。
インスタンス変数@pointsは、そのアカウントが持つポイント残高を保持します。インスタンス変数を使用することで、オブジェクトの状態を保持することができます。
pointsメソッドは、現在のポイント残高を外部から参照するためのメソッド(ゲッターメソッド)です。このようなメソッドを用意することで、オブジェクトの内部状態を安全に外部から確認できるようになります。
ポイント加算機能の実装
次に、ポイントを加算するための機能を追加します。先ほどのコードに、add_pointsメソッドを追加しましょう。
class Account  def initialize(points: 0)    @points = points  end
  def points    @points  end
  def add_points(amount)    @points += amount  endendadd_pointsメソッドは、引数で指定された量だけポイントを増やす処理を行います。+=演算子を使用することで、現在の@pointsの値にamountを加算した結果を、再び@pointsに代入しています。
このような実装により、アカウントのポイント残高を増加させる機能が実現できました。では、この機能が正しく動作するかどうかをテストで確認していきましょう。
changeマッチャーを使用したテストの実装
では、実装したadd_points機能に対するテストを書いていきましょう。spec/account_spec.rbファイルを作成し、以下のテストコードを記述してください。
require_relative '../account'
RSpec.describe Account do  describe '#add_points' do    it 'ポイントを加算できる' do      account = Account.new(points: 100)      expect { account.add_points(30) }.to change { account.points }.by(30)    end  endendこれまでのマッチャーとは異なり、changeマッチャーを使用する際は、通常のexpect()ではなくexpect {}という波括弧を使用しています。これは、「これから実行する処理」をブロックとして指定するためです。
テストの構造は以下のような意味を持ちます。
- expect { account.add_points(30) }:「この処理を実行すると...」
- to change { account.points }:「accountのポイントが変化する」
- by(30):「その変化量は30である」
この書き方により、「メソッドを実行した結果、期待する値の変化が起きたか」を明確にテストすることができます。
ファイルを保存したら、テストを実行してみましょう。
bundle exec rspec spec/account_spec.rbテストが成功すれば、add_pointsメソッドが正しく実装されていることが確認できます。
ポイント使用機能の実装とテスト
続いて、ポイントを使用する機能を追加します。account.rbにuse_pointsメソッドを追加しましょう。
class Account  def initialize(points: 0)    @points = points  end
  def points    @points  end
  def add_points(amount)    @points += amount  end
  def use_points(amount)    if @points >= amount      @points -= amount    end  endendこのメソッドには以下のような条件分岐が含まれています。
- 残高が使用しようとするポイント以上ある場合のみ、ポイントを減らす
- 残高が不足している場合は、何も処理を行わない(ポイントは変化しない)
この機能に対するテストも追加していきましょう。spec/account_spec.rbに以下のテストケースを追加します。
RSpec.describe Account do  # 既存のテストはそのままで...
  describe '#use_points' do    it 'ポイントを使用すると残高が減少する' do      account = Account.new(points: 100)      expect { account.use_points(30) }.to change { account.points }.by(-30)    end
    it '残高が足りない場合は変化しない' do      account = Account.new(points: 10)      expect { account.use_points(30) }.not_to change { account.points }    end  endendこのテストでは、2つの重要なシナリオを検証しています。
- 正常系:ポイント残高が十分にある場合、指定した量だけポイントが減少することをby(-30)で検証
- 異常系:ポイント残高が不足している場合、ポイントが変化しないことをnot_to changeで検証
今回のようにnot_to changeを使用することで、「値が変化しないこと」という要件も明確にテストできます。これは、「期待する動作が起きない」ことを検証する場合に非常に便利です。
実装したテストを実行してみましょう。
bundle exec rspec spec/account_spec.rbテストが成功すれば、use_pointsメソッドも正しく実装されていることが確認できます。
changeマッチャーの応用
changeマッチャーは、より複雑な変化のパターンも検証できます。例えば、「値が特定の範囲に収まること」や「特定の条件を満たすようになること」なども検証可能です。
いくつかの応用例を見てみましょう。例として、ボーナスポイント機能を追加します。account.rbに以下のメソッドを追加してください。
def add_bonus_points(amount)  @points += amount * 2  # ボーナスポイントは2倍endこのメソッドに対するテストは、以下のように書けます。
describe '#add_bonus_points' do  it 'ボーナスポイントを2倍で加算できる' do    account = Account.new(points: 100)    expect { account.add_bonus_points(30) }.to change { account.points }.from(100).to(160)  endendここでは、from(100).to(160)を使用して、「ポイントが100から160に変化すること」を検証しています。これにより、変化前と変化後の具体的な値を指定することができます。
また、複数の値の変化を同時にテストすることもできます。例えば、アカウントをアップグレードする機能を追加し、アップグレード時に複数の属性が変化することを検証するテストなども考えられます。
changeマッチャーのさまざまな使い方
changeマッチャーは、単純な数値の変化だけでなく、さまざまな変化のパターンを検証できます。以下に、いくつかの例を紹介します。
配列の要素数の変化を検証する
# 購入履歴を管理する機能の例def add_purchase_history(item)  @history ||= []  @history << itemend
# テストit '購入履歴が追加される' do  expect { account.add_purchase_history('コーヒー') }.to change { account.history.size }.by(1)end特定の条件を満たすかどうかの変化を検証する
# アカウントのステータスを変更する機能の例def upgrade_to_premium  @status = 'premium'end
# テストit 'プレミアム会員にアップグレードできる' do  expect { account.upgrade_to_premium }.to change { account.premium? }.from(false).to(true)endこのように、changeマッチャーは様々な状況で活用できる非常に便利なマッチャーです。
まとめ
本章では、RSpecのchangeマッチャーについて学習しました。以下の内容をマスターできたことと思います。
- changeマッチャーを使用することで、メソッドの実行による値の変化を明確かつ簡潔にテストできる
- ブロックを使った特殊な構文(expect { ... }.to change { ... })の使い方
- 値が増減する場合だけでなく、値が変化しないケースのテスト方法
- 変化前と変化後の具体的な値を指定する方法
changeマッチャーは、特に以下のような場面で活用できます。
- ポイントや在庫数など、数値の増減をテストする場合
- 配列の要素数の変化をテストする場合
- データベースのレコード数の変化をテストする場合
- オブジェクトの状態変化をテストする場合
次回は、エラーが発生することを検証するためのraise_errorマッチャーについて学習します。例外処理は実際のアプリケーション開発で重要な役割を果たすため、適切にテストすることが大切です。
Basicプランでより詳しく学習
この先のコンテンツを読むにはBasicプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。