subjectとは
RSpecでテストを書くときに使う便利なもので、テストをするときに、テストしたいものを
subjectとして指定する
ものです。
ex)
「猫が好き」という文をテストしたいとします。その場合、subjectはその文の「猫が好き」という部分です。
テストを書くとき、subjectを使うことで、そのテストがどの対象に対してテストするのかがわかりやすくなります。
また、subjectを使うと、同じテストで何度も同じ対象を指定する必要がなくなり、
subjectを使うとテストの中で同じ対象を何度も使い回すことができます。
subjectの使用例
Request spec(APIの使用例)
subjectは主題という意味
で、APIを叩く部分を切り出すこと何を確認するためのテストなのかがわかります。
RSpec.describe "Users", type: :request do ... end
では、
Usersに関するrequest specを記述しています。
これは、HTTPリクエストとそのレスポンスをテストするためのものです。
:request
はこのスペックがリクエストスペックであることを示しています。
require 'rails_helper'
RSpec.describe "Users", type: :request do
describe "GET /users" do
subject { get(users_path) }
it "ユーザーの一覧が取得できる" do
subject
end
end
# 中略
end
-
subject { get(users_path) }
テスト対象となるコードが記述されていて、get(users_path)がsubjectになります。
これは、users_pathに対してGETリクエストを送信することを意味します。 -
it "ユーザーの一覧が取得できる" do ... end
ここで実際のテストが行われ、itブロックは、1つのテストケースを定義します。
このテストケースは、「ユーザーの一覧が取得できる」という仮定をテストしています。 -
subject
ここでsubjectが呼ばれ、GET /usersエンドポイントに対するリクエストが実行されます。
このテストコード全体の流れは、GET /usersエンドポイントが正常に動作するかどうかをテストしてます。subjectを使い、テスト対象のコードを明確に定義し、itブロック内でテストを実行することができます。
他にも使い方を見ていきます。
- 簡単なsubject
RSpec.describe Calculator do
subject { Calculator.new }
it "足し算" do
result = subject.add(6,4)
expect(result).to eq(10)
end
it "引き算" do
result = subject.subtract(10, 6)
expect(result).to eq(4)
end
end
Calculatorクラスのインスタンスをsubjectとして定義し、それを使ってaddメソッドやsubtractメソッドをテストしてます。
- 少し複雑な使い方
RSpec.describe BankAccount do
let(:user) { create(:user) }
subject { described_class.new(user) }
it "最初は残高0" do
expect(subject.balance).to eq(0)
end
it "入金可能" do
subject.deposit(100)
expect(subject.balance).to eq(100p)
end
it "引き落とし可能" do
subject.deposit(1000)
subject.withdraw(500)
expect(subject.balance).to eq(500)
end
end
⚫︎補足
Rubyでbalanceは引き落としたい金額と手数料が残高となる処理
ここでのsubjectの使い道についてです。
subject { described_class.new(user) }
は、
テストで使用する「口座」を作成する部分で、
described_classは現在のテストが対象としているクラス、つまりBankAccountクラスです。
つまり、この行は次のように解釈できます。「このテストでは、BankAccountクラスの新しい口座を作成します。その口座の所有者は、let(:user) { create(:user) }で定義されたユーザーです。」
この口座が作成された後、それを使ってテストを行います。たとえば、「初期残高が0であることを確認する」テストでは、この作成された口座の残高を検査して、それが0であることを確認します。
subjectは何回も使う必要ない?
以下の資料でsubjectを何回も使う必要ないという記事がありました。どういうことでしょうか。🤔
こちらの著者の主張は、subjectは効果的な時しか使わないという記事です。
①subjectが向いているケース(subjectの使いどころ)
def greet
'Hello!'
end
describe '#greet' do
subject { greet }
it { is_expected.to eq 'Hello' }
end
greetメソッドをテストしていて、このメソッドはHello!という文字列を返すメソッドです。
このように戻り値を返すメソッドをsubjectにするといいみたいです。違和感がない感じはします。
②subjectが向いていないケース(ソッドの副作用をテストするケース)
class Counter
attr_reader :count
def initialize
@count = 0
end
def increment
@count += 1
end
end
describe 'Counter#increment' do
let(:counter) { Counter.new }
subject { counter.increment }
it do
subject
expect(counter.count).to eq 1
end
end
これは、Counterクラスのincrementメソッドをテストしていて、
incrementメソッドは、Counterクラスのインスタンス内で保持しているカウント値を増やす副作用を持つメソッドです。
しかし、このテストではsubjectにcounter.incrementを設定しています。
これにより、テストはincrementメソッドの戻り値ではなく、
counter.countの値を検証しています。
before
テストをする前に事前に準備をするための場所です。
テストを始める前に、必要な準備をする場所とも言えそうですね。
小学生にわかりそうな説明をします。
ex)
ケーキを焼くテストをします。ケーキを焼く前にbeforeを使うと、ケーキを焼く前に事前準備ができます。例えば、材料を用意する、道具を揃える、オーブンで温めるなどケーキを焼く前にやることをbeforeの中に書きます。
RSpec.describe "お菓子を焼くテスト" do
before do
@ingredients = ["小麦粉", "砂糖", "卵", "バター"]
@oven_temperature = 180
@tools = ["ボウル", "泡立て器", "オーブン"]
end
it "お菓子が焼けること" do
# お菓子を焼くテストの内容を書く
# ここではお菓子が焼けるかどうかをチェックする
expect(お菓子が焼けるかどうか).to be(true)
end
it "お菓子がおいしく焼けること" do
# お菓子を焼いておいしく焼けるかどうかをチェックするテストの内容を書く
# ここではお菓子がおいしく焼けるかどうかをチェックする
expect(お菓子がおいしく焼けるかどうか).to be(true)
end
end
実際のコードで見てみます。
この例では、beforeブロックの中でお菓子を焼く前の準備をしています。その後、それぞれのテストでその準備を使ってお菓子を焼いたり、おいしく焼けたかどうかを確認したりします。
つまり、beforeを使うと、テストをする前に必要な準備を書いておけば、それを使って簡単にテストができるということです。
RSpec.describe "Users", type: :request do
describe "GET /users" do
subject { get(users_path) }
it "ユーザーの一覧が取得できる" do
3.times { create(:user) }
subject
binding.pry
end
end
# 中略
end
# before句で訂正
RSpec.describe "Users", type: :request do
describe "GET /users" do
subject { get(users_path) }
before { 3.times { create(:user) } }
it "ユーザーの一覧が取得できる" do
subject
binding.pry
end
end
# 中略
end
let
letとはイメージとして変数に値を入れているだけの簡単なもの
⚫︎使い方
let(:user) { create(:user) }
require 'rails_helper'
RSpec.describe "Users", type: :request do
# 中略
describe "GET /users/:id" do
subject { get(user_path(user_id)) }
context "指定した id のユーザーが存在する場合" do
let(:user) { create(:user) }
let(:user_id) { user.id }
it "そのユーザーのレコードが取得できる" do
# 中略
end
end
#省略
end
end
end
RSpecをもっと学ぶなら
資料
ChatGPT