【rspec】スーパークラスのテストの書き方
スーパークラスのテストの書き方が曖昧だったのでChatGPTに聞いてみました。
結論
shared_examplesを使用してスーパークラス(抽象クラス)のテストをサブクラス(具象クラス)にインクルードしてテストする。
前提
- インスタンスの作成にはFactoryBot使用
-
スーパークラスで
abstract=true
を設定しているのでスーパークラスのインスタンスを作成できない(仮にスーパークラスのインスタンスを作成でき、スーパークラスをインスタンス化してテストしても、サブクラスをテストしたことにならない。サブクラスでメソッドをオーバーライドして意図しない挙動になっているかもしれない。)
具体例
スーパークラスとサブクラスの定義
class Vehicle
self.abstract_class = true
def start_engine
'Engine started'
end
end
class Car < Vehicle
end
スーパークラスのテスト
module SharedExamplesVehicle
RSpec.shared_examples 'a Vehicle' do
describe '#start_engine' do
# FactoryBotを使用してインスタンスを作成
let!(:vehicle_instance) { create(described_class.to_s.underscore.to_sym) }
it 'starts the engine' do
expect(vehicle_instance.start_engine).to eq('Engine started')
end
end
describe '#xxxx' do
...
end
end
end
サブクラスのテスト
require 'models/vehicle_spec'
RSpec.describe Car, type: :model do
include SharedExamplesVehicle
# 以下の記述でスーパークラスのshared_examples内のテストが全て実行される
it_behaves_like 'a vehicle'
...
end
ポイント
shared_examples
同様の振る舞いをテストする際に便利です。
詳しくはこちら
described_class.to_s.underscore.to_sym
テスト対象のサブクラスのクラス名を動的に取得して、FactoryBotでインスタンス作成時のファクトリ名として利用します。
-
described_class
テスト対象のサブクラスのクラス名を動的に取得します。
-
クラス名の変換
取得したクラス名(described_class.to_s)をunderscoreメソッドでスネークケースに変換し、さらにto_symでシンボル化します。
この変換により、FactoryBotでのインスタンス生成時に適切なファクトリ名を指定することが可能になります。
-
FactoryBotの設定
FactoryBotで定義したファクトリにおいて、スネークケースのモデル名をaliasesプロパティでエイリアスとして設定します。このエイリアスは、上記のクラス名の変換によって得られた名前と一致している必要があります。
(モデル名のスネークケースをファクトリ名として使っている場合aliasesの設定は不要)
実際に書いたコードではfactory名にモデルの略称を使っていた為、aliases
プロパティでモデル名のスネークケースをエイリアスに設定しました。
FactoryBot.define do
factory :city_industry_hit, class: 'CityIndustryEntityHit', aliases: [:city_industry_entity_hit] do
...
end
end
あとがき
オブジェクト指向設計実践ガイドでも以下のように記述されていました。
「その共通の契約に共有されるテストを書き、すべてのオブジェクトにそのテストをインクルードすることです。」
参考
Sandi Metz. オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 (Japanese Edition)
Discussion