#はじめに
初めてRspecに触れてから2週間が経ち、
ようやく文法やモデル単体テストコードの書き方が掴めてきました。
理解するまで、どのようなプロセスを経てきたか、書いてみたいと思います。
これからRSpecを学ぶ方は、参考にしてみてください。
#どのような人に読んでほしいか
- 簡単なSNSアプリは作れるようになったけど、RSpecを初めて勉強する方
- RSpecを学びたいけど、何から手を付ければいいか分からない方
- モデル単体テストを書きたい方
RSpecの基本的な文法から、モデル単体テストコードの書き方、Factory_botの導入までできるよう、解説していきたいと思います。
#書けるようになるまで道筋
1. 文法をざっくり把握する
2. モデル単体テストの具体例を把握する
3. 自作アプリに書いてみる
4. Factory_botを導入して楽に書けるようにする
#1. 文法をざっくり把握する
RSpecは独特な文法で、Railsを学んできた方にとっては取っつきにくいかと思います。
文法を解説している記事で、おすすめはコチラです↓
[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」]
("https://qiita.com/jnchito/items/42193d066bd61c740612")
先輩エンジニアによると、RSpecを勉強する人なら全員、最初にこの記事に目を通すのだそう。
しかし私の場合、初めて一読したときは、「?」が頭の中に溢れました。
「分かったような分からないような」
「肝心のモデル単体テストのコードはどうやって書くんだろう」
そんな感想を持った方は、ざっくりと以下の点を把握すればいいと思います。
- RSpecはdescribe, content, itなどでグループ化する
- expectの箇所で、期待値と実際の値がパスすれば、仕様通りに実装できていることになる
- 期待値と実際の値は、マッチャ(matcher)で比較する
- マッチャ(matcher)はいろいろあるが、都度調べて模写すればまずはOK
また、最初は理解に苦しむかもしれませんが、
何度も記事に目を通しているうちに、言いたい事が何となく分かってくると思います。
反復あるのみです。
#2. モデル単体テストの具体例を把握する
ここまで読んで、
「文法は何となく分かったけど、1+1のテスト例を出されても、モデル単体テストの書き方が分からない」という感想を持つかと思います。
そこで、次に考えるべきは、「何をどうテストするか」、すなわちテスト仕様です。
と言っても、初心者はいきなりテスト仕様をゼロから考え出すことはできないと思うので、
ここは代表的な具体例を見て、学んでいきましょう。
ここでは、ログイン機能を持つUserモデルを例に、代表的なテスト仕様を紹介します。
・アソシエーションのテスト
・投稿(Post, Bookなどなど)モデルとの関係が1:Nになっていること
・登録のテスト(#create)
・name, email, password, password_confirmationが存在すれば登録できること
・nameが無い場合は登録できないこと
・emailが無い場合は登録できないこと
・passwordがない場合は登録できないこと
・password_confirmationがない場合は登録できないこと
上記は飽くまで一例です。
テスト仕様は、プロダクトの仕様によって決まります。
そのため、上記が絶対必要とか、絶対正解という訳ではないです。
都度、最適なテスト仕様を考えていきましょう。
#3. 自作アプリに書いてみる
ひとまずテスト内容も決まったところで、実際に自作アプリに書いていきましょう。
ここでは、factory_botを使わないコードの書き方を紹介します。
なぜ、factory_botを使わないかというと、factory_botは便利ツールで、コード量を減らすことができます。
便利なのは良いことですが、初学者にとってはまず最初は、
コードを省略せず書いた方が、中身をより深く理解できると思うからです。
以下、上記の仕様についてのコードの例です。
(※RSpecの導入については、多くに記事で解説されているので、ここでは割愛します。)
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'アソシエーションのテスト' do
context 'Postモデルとの関係' do
it '1:Nとなっていること'do
expect(User.reflect_on_association(:posts).macro).to eq :has_many
end
end
end
describe "#create" do
it "name, email, password, password_confirmationが存在すれば登録できること" do
user = User.new(
name: "test",
email: "rspec@test",
password: "123456",
password_confirmation: "123456"
)
expect(user).to be_valid
end
it "passwordがない場合は登録できないこと" do
user = User.new(
name: "test",
email: "rspec@test",
password: "",
)
user.valid?
expect(user.errors[:password]).to include("can't be blank")
end
it "password_confirmationがないと登録できないこと" do
user = User.new(
name: "test",
email: "rspec@test",
password: "123456",
password_confirmation: "",
)
user.valid?
expect(user.errors[:password_confirmation]).to include("doesn't match Password")
end
it "emailがない場合は登録できないこと" do
user = User.new(
name: "test",
email: "",
password: "123456",
password_confirmation: "123456",
)
user.valid?
expect(user.errors[:email]).to include("can't be blank")
end
end
end
Userモデルの空のインスタンスを作成し、それぞれのカラムに値、あるいは空データを入れる、という書き方です。
"bundle exec rspec spec/models"でテストを実行し、
ターミナルに、"5 examples, 0 failures, ~ pending"
と出力されればOKです。
ちなみにpendingとは「保留中」という意味です。
#4. Factory_botを導入して効率的に書けるようにする
上記のような書き方が基本ですが、テスト事にいちいちデータを用意していたのでは面倒ですし、コード量が多くなります。
そこで導入するのがFactory_botというgemです。
導入することで、ダミーのインスタンスを簡単に用意できるようになります。
導入手順は以下の通りです。
①Gemfileに"factory_bot_rails"を追記、bundle installを実行
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails' #追加
end
②specディレクトリ直下に「factories」ディレクトリを作成
③factoriesディレクトリ直下に、「モデル名複数形.rb」ファイルを作成
④データをセット
FactoryBot.define do
factory :user do
name {"taro"}
email {"taro@test"}
password {"123456"}
password_confirmation {"123456"}
end
end
⑤spec/rails-helper.rbに以下を追記
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods #追加
end
これで、FactoryBot::Syntax::Methodsモジュールで定義されているcreateやbuildなどメソッドをテストの中で使用できるようになります。
⑥最後にspec/models/user_spec.rbを書き換えて完成です。
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'アソシエーションのテスト' do
context 'Postモデルとの関係' do
it '1:Nとなっていること'do
expect(User.reflect_on_association(:posts).macro).to eq :has_many
end
end
end
describe "#create" do
it "name, email, password, password_confirmationが存在すれば登録できること" do
user = build(:user)
expect(user).to be_valid
end
it "nameが無い場合は登録できないこと" do
user = build(:user, name: "")
user.valid?
expect(user.errors[:name]).to include("can't be blank")
end
it "emailが無い場合は登録できないこと" do
user = build(:user, email: "")
user.valid?
expect(user.errors[:email]).to include("can't be blank")
end
it "passwordが無い場合は登録できないこと" do
user = build(:user, password: "")
user.valid?
expect(user.errors[:password]).to include("can't be blank")
end
it "password_confirmationが無い場合は登録できないこと" do
user = build(:user, password_confirmation: "")
user.valid?
expect(user.errors[:password_confirmation]).to include("doesn't match Password")
end
end
end
コード量がぐっと減りましたね。
"bundle exec rspec spec/models"でテストを実行し、
ターミナルに、"5 examples, 0 failures, ~ pending"
と出力されればOKです。
以上