はじめに
RSpecとは、Railsのテストフレームワークの1つです。次のような理由から導入することが望ましい考えています。
- 現場で一番使われている
- 可読性の高い記述ができる
- エラー発生を事前に防ぐことができ、結果的にテストを書く方がコスパがよい
- テスト駆動開発(はじめにテストを書く)が注目されている
最終目標は、RailsでAPIを実装する際のRSpecの使い方を理解することです!
最後まで頑張って目を通して下さると幸いです!
(昨今のVueやReactなどのフロントエンドフレームワークの普及により、RailsはAPIモードとして、フロントエンドから送られたパラメータを処理し、その結果をJSONレスポンスとしてフロントエンドに返す役割を担うことが多い。)
皆さんがなるべくストレスなく理解できるよう、以下のように順を追って説明します。
- RSpecを使う上で事前に知っておくこと → この記事
- モデルのバリデーションテスト(モデルスペック)→【Rails API 入門】RSpec ②(予定)
- 単体クラスのテスト →【Rails API 入門】RSpec ③(予定)
- 実装するAPIの処理、レスポンスのテスト(リクエストスペック)→【Rails API 入門】RSpec ④(予定)
RSpecを使う上で事前に知っておくこと
Faker
と FactoryBot
Faker とは
ランダムなデータを、実行するたびに作ってくれるものです。
RSpecでは毎回自分でランダムな値を用意するわけにはいかないので、次に説明するFactoryBot
を含め、これらのgemを使用することが望ましいです。
Faker::Name.name
=> "Francesco Russel"
Faker::Name.name
=> "Junior Dickinson"
このような書き方で、ランダムなデータを毎回作ってくれます。
その他にも、様々な種類のデータを準備することができるので、詳細はFakerのGitHubで調べて見て下さい。
FactoryBot とは
ダミーのレコードを作ってくれるものです(こちらがGitHubページ)。
次のような、商品Product
モデルのダミーレコード作成を例として説明します。
まずはどのようなダミーレコードを作るのかをspec/factories
配下にファイルを作成して記述します。
FactoryBot.define do
factory :product do
name { Faker::Food.fruits }
# 略
end
end
すると、次のような書き方をすることで
FactoryBot.build(:product) # Product.new(name: Faker::Food.fruits)と同じ
=> #<Product:0x0000000000000000
id: nil,
name: "Banana",
created_at: nil,
updated_at: nil>
のようなランダムなデータを作成することができます。
build
は、new
を実行するProduct.new(name: Faker::Food.fruits)
と同じ結果を得ることができるということです。
また同じようなやり方で、Product.create(name: Faker::Name.name)
は
FactoryBot.create(:product) # Product.create(name: Faker::Food.fruits)と同じ
=> #<Product:0x0000000000000000
id: 1,
name: "Lemon",
created_at: Mon, 04 May 2020 07:30:41 UTC +00:00,
updated_at: Mon, 04 May 2020 07:30:41 UTC +00:00>
のようにレコードを作ることができます。
パラメーターとしてハッシュ値を返したい場合は、
FactoryBot.attributes_for(:product) # { name: Faker::Food.fruits }と同じ
=>{:name=>"Pineapple"}
を使用することができます。
ちなみにそれぞれ、FactoryBot.build(:product)
は
build(:product)
FactoryBot.create(:product)
は、
create(:product)
FactoryBot.attributes_for(:product)
は、
attribute_for(:product)
と省略して記載することができます!(以降はこの省略形で説明します。)
let
と before
let とは
変数を格納するような機能です。
これを使うことで可読性が上がる、タイポに気づける等のメリットがあるので、できる限り変数を用意する時は、let
を使うようにして下さい(詳しい理由はこちらを参考にして下さい)。
let(:product) { create(:product) }
=> product # productのインスタンス変数が作られる
ここでは、let
直後の(:product)
で変数product
を定義し、{}
内のcreate(:product)
でランダムなレコードを作成しています(create(:product)
はFactoryBotでランダムなレコードを作成するもので、先程解説した内容です)。
before とは
テストを行いたい処理の前に行っておきたい処理を書くものになります。
例えば、特定の商品(product
レコード)を所有するUser
(has_many
やhas_one
リレーション)を事前に作成しておきたい場合は、テストを行う処理の前に
before { create(:user) }
を記述することで、事前にUser
を作成しておくことができます。
subject
subject とは
テストしたい処理を明示的に書くもので、テストの可読性が向上するものです。
明確に「〇〇処理」を行うテストだよ!と、「主題」(subjectの英語訳)のようにアピールするような役割を持つものです。
後ほど具体例を交えながらの解説を行いますので、ひとまず、テストしたい処理をsubjectに書くと覚えて下さい。
subject { product.valid? }
ですのでこれは、{}
内のproduct.valid?
がテストしたい処理であり、わざわざsubject
として切り分けて書くことで、「このテストはproduct.valid?
の処理をテストしようとしているんだな!」とレビューする人が判断できるようになります(逆に言えば、subjectがなくても同様のテストを実装することは可能なのです)。
ここで言うvalid?
はproduct
インスタンスのバリデーションを実行するメソッドであるので、subjectを使うことで明示的に、「このテストではバリデーションのテストを行いたいのか!」と解釈することができます。
expect
expect とは
expectの英語訳が「期待する」であるように、テストを行うことで期待される処理を書くところです。
例えば、
expect(product.name).to eq "Banana"
があるとすると、expect
直後のproduct.name
が"Banana"
とeq
(等しい)ことを期待する、確認するという処理になります。
処理結果としてはtrue
かfalse
を返し、true
であればテストの成功、false
であればテストの失敗(期待される結果ではない)ということになります。
eq
はマッチャーと呼ばれ、eq
以外にも様々なものがあります。詳しくはこちらを確認して下さい。
具体例
商品モデル(Product
)のname
カラムのバリデーションのテストを想定しましょう。
次のようなバリデーションが設定してあるとします。
class Product < ApplicationRecord
validates :name, presence: true
# 略
end
今であれば、何となく、次に示す**Product
モデルのバリデーションテスト**の内容を理解できるのではないでしょうか?
require "rails_helper"
RSpec.describe Product, type: :model do
describe "バリデーションの確認" do
subject { user.product.valid? }
before { create(:user) }
context "正常なレコードが作られる時" do
let(:product) { create(:product, user: user) }
it "商品を登録することができる" do
expect(subject).to be true
end
end
end
end
RSpec.describe Product, type: :model do
まず、RSpec.describe
後ろのProduct
は、Product モデルを対象としてテストするよという意味合いです(単体クラスのテストではProductクラスのテストという意味になります)。
その後の、type: :model
はモデルのテストを行うよ!という意味で、
APIのテストであるリクエストスペックの場合は、type: :request
のように書き、ここではテストの種類を明示します(モデルとは関係のない単体クラスのテストの場合は省略します)。
その次の行は、
describe "バリデーションの確認" do
と書かれてあります。
このdescribe
は、テストがどういう内容か?を補足する役割を担っており、その他にもcontext
、it
が存在します。それぞれ、
-
describe
はテストの対象や行う処理を表す記述 -
context
はテストの条件を示す記述 -
it
はテスト結果として期待されるものを示す記述
を書く必要があります。
これらのテスト内容を補足する役割がある箇所を抜き出すと、
describe "バリデーションの確認" do
context "正常なレコードが作られる時" do
it "商品を登録することができる" do
end
end
end
となり、上から読み解いていくと、
-
テストの対象や行う処理は
"バリデーションの確認"
で、テストの条件として"正常なレコードが作られる時"
を想定し、期待される結果は商品を登録することができる
であるテスト
の意味になります!
このような理由で、RSpecでは**何をテストしようとしているのか?**を、視覚的に理解しやすく、可読性が高いと言われています。
少し前に戻り、先程説明したdescribe "バリデーションの確認" do
の次の行、subject
です。
subject { user.product.valid? }
このようにsubject
があることで、このテストではuser.product.valid?
の処理をテストしたいんだ!
と一目で理解することができます。
ちなみにこの処理はこの時点では実行されません。実行のタイミングは後でやってきます。
そして、その次の処理、
before { create(:user) }
のbefore
は、テストを行いたい処理の前に行っておきたい処理を書くものでしたね!
ですので、このタイミングでuser
インスタンスが作られます(UserモデルのFactoryBotが実装されてある想定)。
次の
let(:product) { create(:product, user: user) }
はlet
なのでproduct
のインスタンス変数を作っていますが、
create(:product, user: user)
の書き方は、この記事で説明した内容より少し発展したFactoryBot
の使い方です。
これは、先程before { create(:user) }
で作られたuser
を親として持つproduct
インスタンスを作る、という意味になります。
リレーションが関わるレコードを作るのは頻出なので、合わせて覚えていただけると良いと思います!
そして最後にit
の中身を解説します。
it "商品を登録することができる" do
expect(subject).to be true
end
RSpecでは、it
ブロック内に、テストとして確かめたい動作コードを実装し、it
内が成功すればテストの成功、失敗すればテストの失敗、となります。
expect(subject).to be true
は先程のexpectとはで説明した通り、
-
subject
の実行結果が、true
である(be
マッチャー)か
を結果として期待しています。
それではsubject
の実行とは何か?それは、先程定義した、subject { user.product.valid? }
の実行、つまり、
user.product.valid?
であり、user.product
のバリデーション結果がtrue
なのか?それともfalse
なのか?になりますね!
これでやっと、バリデーションのテストが完了したことになります。
(ここまで、大変お疲れ様でした!)
ちなみに、実行結果、つまりターミナルでの出力は、
Product
バリデーションの確認
正常なレコードが作られる時
商品を登録することができる
Finished in 0.12345 seconds (files took 1.23 seconds to load)
1 example, 0 failures
のようになります。
このターミナルの表示を見ると一目でテスト内容が分かり、
- Productモデルのバリデーションの確認を行っており、正常なレコードが作られると、商品を登録することができる!
というテスト結果を確認することができます。
(このターミナル表示は.rspec
ファイルに--format documentation
を書いて設定しており、出力結果の見た目が良くなるようにしています。)
もしも、describe "バリデーションの確認"
の記述を省略している場合は、
Product
正常なレコードが作られる時
商品を登録することができる
のような出力結果となり、なんとなくは分かりますが、具体的なテスト内容が分かりません。
このように必要に応じてdescribe
やcontext
を利用し、テスト結果を誰が見ても分かる状態にするのが理想です!
終わりに
RSpecははじめは覚えることが多く、難しいと感じますが、繰り返し実装を行っていくと、頭の整理が付きやすく、スラスラ書けるようになります。みなさんがそのようになれるよう、この記事も改良していければと思います!
近日、【Rails API 入門】RSpec ②〜④を記事として書きます!頑張ります!