Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

超最低限のRailsアプリをテストする(モデル編)

この記事の基本的な方針

テストは重要です。
なぜなら、人間は不完全であるからです。
ではもし自分が完全であったら、テストは不要でしょうか?
いいえ。なぜなら他人にコードの妥当性を証明しなくてはならないからです。

テストの目的以下です。
 ①コードの妥当性を自分で確認すること
 ②コードの妥当性を他人に示すこと

ここでは別記事、超最低限のRailsアプリを丁寧に作る(もう一度きちんと復習して初心者を卒業しよう)で作ったアプリをテストします。

手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。

Terminal
$ git clone https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate

基本解説はしません。手順のみ示します。

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

具体的な手順

完成品GitHub

①登録条件確認

deviseでデフォルトで設定されたEmailとPasswordの制約を確認します。

見るべきは、(1)devise公式GitHub内のバリデーションに関するファイルと(2)config/initializers/devise.rbです。

(1)devise公式GitHub内のバリデーションに関するファイルには、「Passwordは空ではならない」、「Emailはユニークで空ではならない」とあります。

(2)config/initializers/devise.rbには

config/initializers/devise.rb
#省略
config.password_length = 6..128
#省略
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
#省略

とあります。

よって今回は、

Passwordは、
6文字から128文字である。
Emailは、
ユニークで かつ @の前後に@と空白以外が1文字以上ずつ」である。

を確認すべく、テストします。

②準備

Gemfile
#省略
group :development, :test do
#省略
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

group :development do
#省略
  gem 'spring-commands-rspec'
end
#省略

※spring-commands-rspecは起動時間を速くするためのものでなくても問題はありません。

Terminal
$ 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
.rspec
--format documentation

※これでRspecの出力が読みやすくなるそうです。

ここで空っぽのまま一度起動してみます。

Terminal
$ bundle exec rspec
Output
#省略

Finished in 0.00273 seconds (files took 2.76 seconds to load)
1 example, 0 failures, 1 pending

※1個のテストに対して0個の失敗があり、1個の保留があるという意味です。

③テスト構築

spec/models/user_spec.rb
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
Terminal
$ bundle exec rspec
Output
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
spec/factories/users.rb
FactoryBot.define do
  factory :user do
    email {"a@a"}
    password {"111111"}
    password_confirmation {"111111"}
  end
end
spec/models/user_spec.rb
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
Terminal
$ bundle exec rspec
Output
#省略

8 examples, 0 failures, 7 pending

エラー文は以下のように確認します。

Terminal
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}]}>

④テスト本番

spec/models/user_spec.rb
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
Terminal
$ bundle exec rspec
Output
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桁まで考えると気が遠くなるパターンがあることがわかります。
そもそも全パターン試せるようなパスワードがあればパスワードとしての価値がありません。
物理的に全パターンを試すことが出来ない我々は、限界値の両端をうまくテストしなくてはなりません。

annaPanda8170
長いコードを"上から順に"説明されても理解できないですよね? "必要な順に"コードを増やしてゆくスタイルで記事を書いていくつもりです。 私の記事は半分パブリックなものだと思っているので、どんどん意見を吸収して育てていきたいです。
https://annapanda.xyz/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away