#初めに
Ruby on Railsのテストにはmini testではなくrspecが用いられることが多いようで実際に会社で働こうと思ったらrpsecについて知っておかなくてはいけないと思い勉強したことの備忘録。
いろいろ間違っているかも
#設定
とりあえずgemfileに
gem 'rspec-rails'
と書いてbundle installして
rails g rspec:install
を行えばrspecに関係するファイルが作られる。
config/application.rbに
config.generators do |g|
g.test_framework :rspec
end
と書くとモデルやらコントローラーやらを作成したときにそれに関係するrspecのファイルを作ってくれる。
逆にrspecのファイルを自動的に作ってほしくないときは
config.generators do |g|
g.test_framework false
end
とかいたらok
controllerに関するrpsecのファイルは作成しなくてよいけど他のは作ってほしいみたいなときは
config.generators do |g|
g.test_framework :rspec,
controller_specs: false
end
みたいに設定できる
rspecファイルを作りたい場合は
rails g rspec:model user
みたいな感じで書く。この場合はuser のmodelに対するrspecのファイルを作成する。
ほかにも
rails g rspec:job users
rails g rspec:view users
rails g rspec:helper users
rails g rspec:feature users
みたいなかんじでできる。
ルートディレクトリに.rspecというファイルが作成されているがそこでrspecの出力に関する設定ができる。
とりあえず
--require spec_helper
--color
--format d
こうしたらいい感じになる。
rspecの実行に関しては
rspec
もしくは
bundle exec rspec
でいけるとおもう。
#具体的になにをするか先に書く
こんな感じでなにをするかさきに書く
require 'rails_helper'
RSpec.describe User, type: :model do
it "名前とメールアドレスとパスワードがあれば登録できる"
it "名前がなければ登録できない"
it "メールアドレスがなければ登録できない"
it "メールアドレスが重複していたら登録できない"
it "パスワードがなければ登録できない"
it "パスワードが暗号化されているか"
end
この状態で実行すると
User
名前とメールアドレスとパスワードがあれば登録できる (PENDING: Not yet implemented)
名前がなければ登録できない (PENDING: Not yet implemented)
メールアドレスがなければ登録できない (PENDING: Not yet implemented)
メールアドレスが重複していたら登録できない (PENDING: Not yet implemented)
パスワードがなければ登録できない (PENDING: Not yet implemented)
パスワードが暗号化されているか (PENDING: Not yet implemented)
Pending: (Failures listed here are expected and do not affect your suite's status)
1) User 名前とメールアドレスとパスワードがあれば登録できる
# Not yet implemented
# ./spec/models/user_spec.rb:5
2) User 名前がなければ登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:7
3) User メールアドレスがなければ登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:9
4) User メールアドレスが重複していたら登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:11
5) User パスワードがなければ登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:13
6) User パスワードが暗号化されているか
# Not yet implemented
# ./spec/models/user_spec.rb:15
Finished in 0.00456 seconds (files took 0.95882 seconds to load)
6 examples, 0 failures, 6 pending
みたいに出る。
これので一つ一つテストを成功させていく。
#データ作成をfactory_bot_railsで行う。
テストに使うデータの作成にfactory_bot_railsというgemを使う
とりあえず
gem 'factory_bot_rails'
bundleする。
spec/rails_helper.rbに
config.include FactoryBot::Syntax::Methods
と書いておく
コマンドで
rails g factory_bot:model user
と書けば必要なファイルが作成される
factories/users.rbに
FactoryBot.define do
factory :user do
name {"hiro"}
sequence(:email) { |n| "hiro#{n}@example.com"}
password {"password"}
end
end
こんな感じで書いておくと
user = FactoryBot.create(:user)
でデータを作成してくれる
なぜemail {"hiro@example.com"}ではないかというと
emailは一意制約を付けているので
user1 = FactoryBot.create(:user)
user2 = FactoryBot.create(:user)
とするとエラーが出てしまう
sequence(:email) { |n| "hiro#{n}@example.com"}
とかけばエラーが避けられる
spec/models/user_spec.rbに具体的な処理を書く
require 'rails_helper'
RSpec.describe User, type: :model do
it "名前とメールアドレスとパスワードがあれば登録できる" do
expect(FactoryBot.create(:user)).to be_valid
end
it "名前がなければ登録できない" do
expect(FactoryBot.build(:user, name: "")).to_not be_valid
end
it "メールアドレスがなければ登録できない" do
expect(FactoryBot.build(:user, email: "")).to_not be_valid
end
it "メールアドレスが重複していたら登録できない" do
user1 = FactoryBot.create(:user,name: "taro", email: "taro@example.com")
expect(FactoryBot.build(:user, name: "ziro", email: user1.email)).to_not be_valid
end
it "パスワードがなければ登録できない" do
expect(FactoryBot.build(:user, password: "")).to_not be_valid
end
it "パスワードが暗号化されているか" do
user = FactoryBot.create(:user)
expect(user.password_digest).to_not eq "password"
end
it "password_confirmationとpasswordが異なる場合保存できない" do
expect(FactoryBot.build(:user,password:"password",password_confirmation: "passward")).to_not be_valid
end
end
とりあえずbundle exec rspecをすると....
User
名前とメールアドレスとパスワードがあれば登録できる (FAILED - 1)
名前がなければ登録できない (FAILED - 2)
メールアドレスがなければ登録できない (FAILED - 3)
メールアドレスが重複していたら登録できない (FAILED - 4)
パスワードがなければ登録できない (FAILED - 5)
パスワードが暗号化されているか (FAILED - 6)
Failures:
1) User 名前とメールアドレスとパスワードがあれば登録できる
Failure/Error: expect(FactoryBot.create(:user)).to be_valid
NoMethodError:
undefined method `password=' for #<User:0x00007fffbd4d20f0>
Did you mean? password_digest=
# ./spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)>'
2) User 名前がなければ登録できない
Failure/Error: expect(FactoryBot.create(:user, name: "")).to_not be_valid
NoMethodError:
undefined method `password=' for #<User:0x00007fffbd5462c0>
Did you mean? password_digest=
# ./spec/models/user_spec.rb:10:in `block (2 levels) in <top (required)>'
3) User メールアドレスがなければ登録できない
Failure/Error: expect(FactoryBot.create(:user, email: "")).to_not be_valid
NoMethodError:
undefined method `password=' for #<User:0x00007fffbd54d070>
Did you mean? password_digest=
# ./spec/models/user_spec.rb:14:in `block (2 levels) in <top (required)>'
4) User メールアドレスが重複していたら登録できない
Failure/Error: user1 = FactoryBot.create(:user,name: "taro", email: "taro@example.com")
NoMethodError:
undefined method `password=' for #<User:0x00007fffbc254b08>
Did you mean? password_digest=
# ./spec/models/user_spec.rb:18:in `block (2 levels) in <top (required)>'
5) User パスワードがなければ登録できない
Failure/Error: expect(FactoryBot.create(:user, password: "")).to_not be_valid
NoMethodError:
undefined method `password=' for #<User:0x00007fffbd56fa80>
Did you mean? password_digest=
# ./spec/models/user_spec.rb:23:in `block (2 levels) in <top (required)>'
6) User パスワードが暗号化されているか
Failure/Error: user = FactoryBot.create(:user)
NoMethodError:
undefined method `password=' for #<User:0x00007fffbd57b6a0>
Did you mean? password_digest=
# ./spec/models/user_spec.rb:27:in `block (2 levels) in <top (required)>'
Finished in 0.01216 seconds (files took 0.9921 seconds to load)
6 examples, 6 failures
Failed examples:
rspec ./spec/models/user_spec.rb:5 # User 名前とメールアドレスとパスワードがあれば登録できる
rspec ./spec/models/user_spec.rb:9 # User 名前がなければ登録できない
rspec ./spec/models/user_spec.rb:13 # User メールアドレスがなければ登録できない
rspec ./spec/models/user_spec.rb:17 # User メールアドレスが重複していたら登録できない
rspec ./spec/models/user_spec.rb:22 # User パスワードがなければ登録できない
rspec ./spec/models/user_spec.rb:26 # User パスワードが暗号化されているか
こんな感じでエラーがでる。
モデルファイルを修正してエラーが出ないようにする
models/user.rbに
class User < ApplicationRecord
has_secure_password
validates :name, presence: true
validates :password, presence: true
validates :email, presence: true
validates :email, uniqueness: true
end
こう書けばエラーが消えで全部緑で表示される
基本的にexpect()のところに検証したいものを入れて
そのあとにbe_validであれば有効か無効かを検証したり
eqであれば等しいかどうかを検証する。
expect().toのところをto_notみたいにもできる
#FactoryBot.create FactoryBot.buildの違い
buildはデータを実際に保存する必要がない場合や属性の検証のときに使う。
今回の場合は全部buildで大丈夫
#let
letを使えば何回も同じコードを書かないですむ
例えば
let(:user) { FactoryBot.build(:user) }
とすればそのあとは
userと書くだけで{ FactoryBot.build(:user) }が実行されてデータを作ってくれる。
#let!
let!にするとその場でテストデータを作ってくれる。
letの場合はそれが呼び出される時にコードを実行する
#before
beforeはテストが実行される前に行われるもので基本的なデータをbeforeで作ったりどのテストでも行われているログイン処理とかをかいてテストを簡潔にできる。
書き方は
before do
@user = User.new(name: "test", email: "test@gmail.com", password: "password")
@user.save
end
みたいな感じ。
個人的にモデル作成に関してはletを使えばいいかなと思う
ログイン処理とかに関してはbeforeを使うってかんじなのかな?わからないけど....
#FeatureSpec と System Spec
昔はFeatureSpecが使われてて今っぽいのがSystemSpecらしい
具体的に何をするものかというと
例えばユーザー登録画面を開いて入力欄に文字をいれて登録ボタンをおしてユーザー登録が成功したか
みたいなのを検証するものがsystem specでそういったテストを統合テストとよぶ
systemspecを行おうまえにseleliumwebdriverやらchromedriverやらの設定が必要
それに関してはこちらを参考にしてください
EC2 UbuntuでGoogle Chromeをヘッドレス実行してスクリーンショットを採取する手順
https://qiita.com/shinsaka/items/37436e256c813d277d6d
ページにアクセスする場合は
visit root_path
みたいに書いて
入力は
fill_in "Name", with: "Hiroto"
fill_in "Email", with: "hiroto@gmail.com"
みたいな感じ
ボタンクリックは
click_button "Create user"
見たいな感じ
たしか<%= f.label :*** %>のところに書いてある文字をfill_inで指定してwithで入力する文字を指定する感じ
id="***"で指定することもできる
検証の仕方は
expect(page).to have_content "hello world"
で訪れたページにhello worldという文字があるか検証できる
ユーザー登録ができているか確認する場合はこんな感じ
require 'rails_helper'
describe 'ユーザー登録', type: :system do
before do
driven_by :selenium_chrome_headless
end
it "ユーザー登録ができるか" do
expect {
visit new_user_url
fill_in "Name", with: "yuki"
fill_in "Email", with: "yuki@gmail.com"
fill_in "Password", with: "password"
fill_in "Password confirmation", with: "password"
click_button "submit"
expect(page).to have_content "index"
}.to change(User,:count).by(1)
end
end
ここで重要なのがexpect {}の中にexpect(page).to have_content "index"と書いている
これを書かないとclick_buttonが押された後処理が実行される前にchange(User,:count).by(1)を検証してしまうためテストが失敗する。
大体こんな感じだと思う。
feature specを使ったときはitのところにscinarioと書いたりdatabase_cleanerをgem installしたりしたけどsystem specの場合はいらないみたい。
なにか間違っているところがあればご指摘いただけるとありがたいです。