subjectでテスト対象の処理を効率よく書こう

学習の目標

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

  • RSpecのsubjectの概念と、テストデータを効率的に管理する方法について理解する
  • テストコードの重複を排除し、メンテナンス性を向上させる手法を習得する
  • 名前付きsubjectを使用して、テストコードの可読性を向上させる方法を学ぶ
  • テストケース間でのデータ共有の方法とそのメリットについて理解する
  • 実際のプロジェクトでの効果的なsubjectの活用法を身につける

はじめに

皆さん、これまでのチャプターでRSpecの基本的な使い方を学んできましたね。テストを書いていると、同じオブジェクトを何度も作成したり、同じメソッドを何度も呼び出したりすることがよくあります。こういったコードの繰り返しは、テストの可読性や保守性を下げてしまうんです。

そこで今回は、テスト対象となるオブジェクトを効率的に扱うためのsubjectについて説明します。subjectを使えば、テストで繰り返し利用するオブジェクトをスマートに管理できるようになりますよ。

subjectは、テストデータを管理するための便利な機能です。これを使うことで、テストケース間でデータを共有でき、一度定義したデータを複数のテストで再利用することができます。また、コードの重複を防ぐことができるため、テストコードをより簡潔に保つことができるんです。

この機能を使うことで、以下のようなうれしいメリットがあります。

  • テストコードの記述が簡潔になる
  • メンテナンス性が向上する
  • テストの意図がより明確になる

それでは、具体的なコードを見ながら学んでいきましょう!

subjectの基本的な使い方

まずは、簡単な例を通してsubjectの基本的な使い方を見ていきましょう。新しくgreeter.rbというファイルを作成し、簡単な挨拶を返すプログラムを実装します。

class Greeter
def hello
'Hello'
end
end

このコードでは、Greeterクラスにhelloメソッドが実装されており、単純に「Hello」という文字列を返す処理を行います。とてもシンプルですね。

では、このクラスに対するテストを書いていきましょう。まずは、subjectを使わない一般的な書き方でテストを実装してみます。

従来の記述方法

spec/greeter_spec.rbというファイルを作成し、以下のコードを記述してください。

require_relative '../greeter'
RSpec.describe Greeter do
describe '#hello' do
it 'Helloという文字列を返す' do
greeter = Greeter.new
expect(greeter.hello).to eq('Hello')
end
it 'Helloはnilではない' do
greeter = Greeter.new
expect(greeter.hello).not_to be_nil
end
it 'Helloは空文字ではない' do
greeter = Greeter.new
expect(greeter.hello).not_to be_empty
end
end
end

このテストコードを見てみると、各テストケースで同じような処理が繰り返されていることに気づきませんか?具体的には、各テストでGreeterのインスタンスを作成し、helloメソッドを呼び出す部分が重複しています。

テストケースが増えれば増えるほど、この重複も増えていきます。そうなると、もしクラスの構造が変わった場合、すべてのテストケースを修正する必要が出てきて大変ですよね。

subjectを使用した改善

この重複を解消するために、subjectを活用してみましょう。spec/greeter_spec.rbを以下のように修正します。

require_relative '../greeter'
RSpec.describe Greeter do
describe '#hello' do
subject { Greeter.new.hello }
it 'Helloという文字列を返す' do
expect(subject).to eq('Hello')
end
it 'Helloはnilではない' do
expect(subject).not_to be_nil
end
it 'Helloは空文字ではない' do
expect(subject).not_to be_empty
end
end
end

この例では、subject { Greeter.new.hello }と定義しています。これにより、テスト内でsubjectと書くだけで、Greeter.new.helloの結果を参照できるようになりました。

各テストケースで毎回Greeterのインスタンスを作成する必要がなくなり、コードがすっきりしましたね。これがsubjectの基本的な使い方です。

テストコードが簡潔になっただけでなく、「何をテストしているのか」という意図も明確になりました。テスト対象(subject)はGreeter.new.helloであり、それが特定の条件を満たすかどうかをテストしていることが一目でわかります。

それでは、テストを実行してみましょう。

bundle exec rspec spec/greeter_spec.rb

テストが成功すれば、subjectを使った書き方でも正しくテストできていることが確認できますね。

名前付きsubjectの使用

subjectには名前をつけることもできます。これにより、テストコードの可読性がさらに向上します。特に、複数のsubjectを使う場合や、subjectの意味を明確にしたい場合に役立ちます。

spec/greeter_spec.rbを以下のように修正して、名前付きsubjectを使ってみましょう。

require_relative '../greeter'
RSpec.describe Greeter do
describe '#hello' do
subject(:greeting) { Greeter.new.hello }
it 'Helloという文字列を返す' do
expect(greeting).to eq('Hello')
end
it 'Helloはnilではない' do
expect(greeting).not_to be_nil
end
it 'Helloは空文字ではない' do
expect(greeting).not_to be_empty
end
end
end

ここでは、subject(:greeting) { Greeter.new.hello }というように、subject:greetingという名前をつけています。これにより、テスト内でgreetingという名前で参照できるようになりました。

名前付きsubjectを使用することで、テストコードの意図がより明確になり、可読性が向上しています。「何をテストしているのか」がより自然な形で表現できるようになりました。

ただし、シンプルなテストケースでは名前なしのsubjectでも十分わかりやすいこともあります。複雑さに応じて使い分けるとよいでしょう。最初のうちは名前なしのsubjectから慣れていき、徐々に名前付きsubjectも使っていくことをおすすめします。

複雑な例:ユーザー認証の検証

もう少し実践的な例として、ユーザー認証をテストするケースを考えてみましょう。まず、以下のようなUserクラスを作成します。

class User
attr_reader :name, :admin
def initialize(name, admin = false)
@name = name
@admin = admin
end
def admin?
@admin
end
def greeting
if admin?
"Hello, admin #{@name}!"
else
"Hello, #{@name}!"
end
end
end

このクラスに対するテストを、subjectを活用して書いてみましょう。

require_relative '../user'
RSpec.describe User do
describe '#greeting' do
context '一般ユーザーの場合' do
subject(:user) { User.new('John') }
it '通常の挨拶を返す' do
expect(user.greeting).to eq('Hello, John!')
end
it '管理者ではない' do
expect(user.admin?).to be false
end
end
context '管理者ユーザーの場合' do
subject(:admin) { User.new('Admin', true) }
it '管理者向けの挨拶を返す' do
expect(admin.greeting).to eq('Hello, admin Admin!')
end
it '管理者である' do
expect(admin.admin?).to be true
end
end
end
end

このテストでは、一般ユーザーと管理者ユーザーの2つのケースを、それぞれ別のcontextで表現しています。そして、各contextで異なるsubjectを定義しています。

一般ユーザーのテストではsubject(:user) { User.new('John') }、管理者ユーザーのテストではsubject(:admin) { User.new('Admin', true) }という形で、それぞれのコンテキストに合わせたsubjectを定義しています。

このように、contextsubjectを組み合わせることで、様々なケースを明確に区別し、テストコードの構造をより理解しやすくすることができます。

まとめ

今回は、RSpecのsubjectについて学びました。subjectを使うことで、テストコードの重複を減らし、より読みやすく保守しやすいテストを書くことができます。特に以下のポイントを覚えておきましょう。

  • subjectを使うと、テスト対象のオブジェクトを一箇所で定義でき、テストコードの重複を防げる
  • 名前付きsubjectを使うと、テストの意図がより明確になり、可読性が向上する
  • contextと組み合わせることで、異なる状況下でのテストを効率的に書ける
  • 複雑なテストほどsubjectの恩恵が大きいが、シンプルなテストでも活用できる

subjectはRSpecの中でも特に便利な機能の一つで、実際のプロジェクトでも頻繁に使われています。ぜひマスターして、効率的で読みやすいテストコードを書けるようになりましょう!

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

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

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

作成者:とまだ
Previous
raise_errorマッチャーでエラー処理を検証しよう