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

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

```
テストの目的以下です。
　①コードの妥当性を自分で確認すること
　②コードの妥当性を他人に示すこと
````
ここでは別記事、[超最低限のRailsアプリを丁寧に作る（もう一度きちんと復習して初心者を卒業しよう）](https://qiita.com/annaPanda8170/items/8856425be9d563fbffa7)で作ったアプリをテストします。

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

```bash: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](https://github.com/annaPanda8170/minimum_rails_application/tree/%E8%B6%85%E6%9C%80%E4%BD%8E%E9%99%90%E3%81%AERails%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B%EF%BC%88%E3%83%A2%E3%83%87%E3%83%AB%E7%B7%A8%EF%BC%89)
## ①登録条件確認
deviseでデフォルトで設定されたEmailとPasswordの制約を確認します。

見るべきは、(1)[devise公式GitHub内のバリデーションに関するファイル](https://github.com/heartcombo/devise/blob/master/lib/devise/models/validatable.rb)と(2)`config/initializers/devise.rb`です。

(1)[devise公式GitHub内のバリデーションに関するファイル](https://github.com/heartcombo/devise/blob/master/lib/devise/models/validatable.rb)には、「Passwordは空ではならない」、「Emailはユニークで空ではならない」とあります。

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

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

とあります。

よって今回は、

```
Passwordは、
6文字から128文字である。
```

```
Emailは、
ユニークで　かつ　@の前後に@と空白以外が1文字以上ずつ」である。
```
を確認すべく、テストします。

## ②準備

```ruby: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: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:Terminal
$ bundle exec rspec
```

```terminal:Output
#省略

Finished in 0.00273 seconds (files took 2.76 seconds to load)
1 example, 0 failures, 1 pending
```
※1個のテストに対して0個の失敗があり、1個の保留があるという意味です。

## ③テスト構築

```ruby: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:Terminal
$ bundle exec rspec
```

```terminal: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
```

```ruby:spec/factories/users.rb
FactoryBot.define do
  factory :user do
    email {"a@a"}
    password {"111111"}
    password_confirmation {"111111"}
  end
end
```

```ruby: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:Terminal
$ bundle exec rspec
```

```terminal:Output
#省略

8 examples, 0 failures, 7 pending
```

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

```terminal: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}]}>
```

## ④テスト本番

```ruby: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:Terminal
$ bundle exec rspec
```

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