この記事の基本的な方針
テストは重要です。
なぜなら、人間は不完全であるからです。
ではもし自分が完全であったら、テストは不要でしょうか?
いいえ。なぜなら他人にコードの妥当性を証明しなくてはならないからです。
テストの目的以下です。
①コードの妥当性を自分で確認すること
②コードの妥当性を他人に示すこと
ここでは別記事、超最低限のRailsアプリを丁寧に作る(もう一度きちんと復習して初心者を卒業しよう)で作ったアプリをテストします。
手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。
$ git clone https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate
基本解説はしません。手順のみ示します。
想定する読み手
既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。
具体的な手順
①登録条件確認
deviseでデフォルトで設定されたEmailとPasswordの制約を確認します。
見るべきは、(1)devise公式GitHub内のバリデーションに関するファイルと(2)config/initializers/devise.rb
です。
(1)devise公式GitHub内のバリデーションに関するファイルには、「Passwordは空ではならない」、「Emailはユニークで空ではならない」とあります。
(2)config/initializers/devise.rb
には
#省略
config.password_length = 6..128
#省略
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
#省略
とあります。
よって今回は、
Passwordは、
6文字から128文字である。
Emailは、
ユニークで かつ @の前後に@と空白以外が1文字以上ずつ」である。
を確認すべく、テストします。
②準備
#省略
group :development, :test do
#省略
gem 'rspec-rails'
gem 'factory_bot_rails'
end
group :development do
#省略
gem 'spring-commands-rspec'
end
#省略
※spring-commands-rspecは起動時間を速くするためのものでなくても問題はありません。
$ bundle install
$ rails g rspec:install
$ rails g rspec:model user
$ rails g factory_bot:model user
$ bundle exec spring binstub rspec
control + c
$ rails s
--format documentation
※これでRspecの出力が読みやすくなるそうです。
ここで空っぽのまま一度起動してみます。
$ bundle exec rspec
#省略
Finished in 0.00273 seconds (files took 2.76 seconds to load)
1 example, 0 failures, 1 pending
※1個のテストに対して0個の失敗があり、1個の保留があるという意味です。
③テスト構築
require 'rails_helper'
RSpec.describe User, type: :model do
it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる"
it "Passwordが5文字で登録できない"
#パスワード文字数上限の方は省きます
it "passwordとpassword_confirmationが異なっていると登録できない"
it "Emailが@がないと登録できない"
it "Emailが@が二つあると登録できない"
it "Emailが途中に空白があると登録できない"
it "2人のユーザーについて、Emailがユニークであれば登録できる"
it "2人のユーザーについて、Emailがユニークでなければ登録できない"
end
$ bundle exec rspec
2020-03-20 00:51:43 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
User
Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる (PENDING: Not yet implemented)
Passwordが5文字で登録できない (PENDING: Not yet implemented)
passwordとpassword_confirmationが異なっていると登録できない (PENDING: Not yet implemented)
Emailが@がないと登録できない (PENDING: Not yet implemented)
Emailが@が二つあると登録できない (PENDING: Not yet implemented)
Emailが途中に空白があると登録できない (PENDING: Not yet implemented)
2人のユーザーについて、Emailがユニークであれば登録できる (PENDING: Not yet implemented)
2人のユーザーについて、Emailがユニークでなければ登録できない (PENDING: Not yet implemented)
Pending: (Failures listed here are expected and do not affect your suite's status)
1) User Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる
# Not yet implemented
# ./spec/models/user_spec.rb:4
2) User Passwordが5文字で登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:5
3) User passwordとpassword_confirmationが異なっていると登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:7
4) User Emailが@がないと登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:8
5) User Emailが@が二つあると登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:9
6) User Emailが途中に空白があると登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:10
7) User 2人のユーザーについて、Emailがユニークであれば登録できる
# Not yet implemented
# ./spec/models/user_spec.rb:11
8) User 2人のユーザーについて、Emailがユニークでなければ登録できない
# Not yet implemented
# ./spec/models/user_spec.rb:12
Finished in 0.00215 seconds (files took 2.4 seconds to load)
8 examples, 0 failures, 8 pending
FactoryBot.define do
factory :user do
email {"a@a"}
password {"111111"}
password_confirmation {"111111"}
end
end
require 'rails_helper'
RSpec.describe User, type: :model do
it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" do
expect(FactoryBot.build(:user)).to be_valid
end
it "Passwordが5文字で登録できない"
#パスワード文字数上限の方は省きます
it "passwordとpassword_confirmationが異なっていると登録できない"
it "Emailが@がないと登録できない"
it "Emailが@が二つあると登録できない"
it "Emailが途中に空白があると登録できない"
it "2人のユーザーについて、Emailがユニークであれば登録できる"
it "2人のユーザーについて、Emailがユニークでなければ登録できない"
end
$ bundle exec rspec
#省略
8 examples, 0 failures, 7 pending
エラー文は以下のように確認します。
irb(main):001:0> user = User.new(email: "a@a", password: "11111", password_confirmation: "11111")
(0.9ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, email: "a@a", created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'a@a' LIMIT 1
=> false
irb(main):003:0> user.errors
=> #<ActiveModel::Errors:0x00007fd9f12cc6f8 @base=#<User id: nil, email: "a@a", created_at: nil, updated_at: nil>, @messages={:password=>["is too short (minimum is 6 characters)"]}, @details={:password=>[{:error=>:too_short, :count=>6}]}>
④テスト本番
require 'rails_helper'
RSpec.describe User, type: :model do
it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" do
expect(FactoryBot.build(:user)).to be_valid
end
it "Passwordが5文字で登録できない" do
user = FactoryBot.build(:user, password: "11111", password_confirmation: "11111")
user.valid?
expect(user.errors[:password]).to include("is too short (minimum is 6 characters)")
end
#パスワード文字数上限の方は省きます
it "passwordとpassword_confirmationが異なっていると登録できない" do
user = FactoryBot.build(:user, password: "111111", password_confirmation: "211111")
user.valid?
expect(user.errors[:password_confirmation]).to include("doesn't match Password")
end
it "Emailが@がないと登録できない" do
user = FactoryBot.build(:user, email: "aaa")
user.valid?
expect(user.errors[:email]).to include("is invalid")
end
it "Emailが@が二つあると登録できない" do
user = FactoryBot.build(:user, email: "a@@a")
user.valid?
expect(user.errors[:email]).to include("is invalid")
end
it "Emailが途中に空白があると登録できない" do
user = FactoryBot.build(:user, email: "a @a")
user.valid?
expect(user.errors[:email]).to include("is invalid")
end
it "2人のユーザーについて、Emailがユニークであれば登録できる" do
FactoryBot.create(:user)
expect(FactoryBot.build(:user, email: "b@b")).to be_valid
end
it "2人のユーザーについて、Emailがユニークでなければ登録できない" do
FactoryBot.create(:user)
user = FactoryBot.build(:user)
user.valid?
expect(user.errors[:email]).to include("has already been taken")
end
end
$ bundle exec rspec
2020-03-20 02:01:54 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.
User
Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる
Passwordが5文字で登録できない
passwordとpassword_confirmationが異なっていると登録できない
Emailが@がないと登録できない
Emailが@が二つあると登録できない
Emailが途中に空白があると登録できない
2人のユーザーについて、Emailがユニークであれば登録できる
2人のユーザーについて、Emailがユニークでなければ登録できない
Finished in 0.06768 seconds (files took 2.31 seconds to load)
8 examples, 0 failures
まとめ
もし、パスワード全パターンを試すようなことができれば完璧なテストですが、それはできません。
例えば6桁に限定したとしても、数字10種とアルファベット26文字の大文字と小文字で計算すると、
(10 + 26 + 26)^6 = 56,800,235,584
つまり500億パターン以上で、これを128桁まで考えると気が遠くなるパターンがあることがわかります。
そもそも全パターン試せるようなパスワードがあればパスワードとしての価値がありません。
物理的に全パターンを試すことが出来ない我々は、限界値の両端をうまくテストしなくてはなりません。