[単体テスト]railsのテスト カタカナのテストコードに苦しめられた　結果：全ては勘違いだった

#テスト
モデルのバリデーションテストについて

**内容**
1,Gemをインストール
2,RSpecの設定
3,テストを書く(主にバリデーション)
4,テストを実行
5,各テストの見本コード
6,もっと記述を簡単にするためのGemインストール
7,factorybotの準備
8,factory_botの記法の省略
9,終わりに
参考ページ




##1,Gemfileの下部に追加

```ruby:Gemfile
gem 'rspec-rails'

#こちらはもともと記述があるかもしれないので
#重複しないように注意（group :development doのなかに入るようにする）
group :development do
  gem 'web-console'
end
```
`bundle install`コマンド実行

##2,RSpecの設定
Rspecは設定ファイルを作成する必要がある

```ruby:ターミナル
$ rails g rspec:install
```
以下が作成される
**.rspec**
**spec**
**spec/spec_helper.rb**・・・「rails_helper.rbと同じくRSpec用の共通の設定を書いておくファイルですが、こちらはRSpecをRails無しで利用する際に利用します。」
**spec/rails_helper.rb**・・「RailsにおいてRSpecを利用する際に、共通の設定を書いておくファイルです。各テスト用ファイルでこちらのファイルを読み込むことで、共通の設定や、メソッドを適用します。」

記入する

```ruby:.rspec
--format documentation
```

##3,テストを書く
記述をするファイルはappディレクトリ以下にあるテストの対象となるコードの在り処と対応させるように作成する

※specファイルの命名規則
specファイルは対応するクラス名_spec.rbという名前

[app以下]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540209/d338bfc2-1974-9464-24e1-1f03fbb39d5f.png)
[spec内にあるテスト設定用ファイル]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540209/81d6cf9f-e165-1183-d182-c3376056b56d.png)



**describe**
1行目のdescribeは、直後のdo ~ endまでのテストのまとまりを作ります。describeの後に続く""の中にはそのまとまりの説明を書きます。

**itとexample**
2行目のitはexampleと呼ばれる実際に動作するテストコードのまとまりを表します。itの後に続く""の中にはそのexampleの説明を書きます。

**エクスペクテーション**
実際に評価される式のことです。it do ~ endの間に書きます。

**expect(X).to eq Y**
エクスペクテーションの文法です。xの部分に入れた式の値がYの部分の値と等しければ、テストが成功します。eqの部分を、マッチャと言います。

**マッチャ**
エクスペクテーションの中で、テストが成功する条件を示します。例えばeqは「等しければ」という意味になります。他にも
include(含んでいれば)、valid(バリデーションされれば)など複数のマッチャが存在します。

[使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540209/346b9984-2196-47fd-11e6-2a7b8fa67150.png)

**rails cのやり方**

```ruby:ターミナル
$ rails c
pry > user = User.new(nickname: "", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000", first_name: "a", last_name: "a", birth_year: "2020", birth_month: "1", birth_day: "1", last_name_kana: "カナ", first_name_kana: "カナ")
pry > user.valid?
pry > user.errors

@details={:nickname=>[{:error=>:blank}]},
@messages={:nickname=>["を入力してください"]}>
```


##4,テストを実行
全体テストの実行

```
$ bundle exec rspec
```
単体テスト実行

```
#rspec 以降にフォルダとファイル名を指定する
$ bundle exec rspec spec/models/user_spec.rb
```

##5,各テストの見本コード

####各項目のバリデーションチェックについて
**nicknameが空じゃないか確かめるコード**
私は日本語化してしまっているので@message以降のあたいは日本語で入力しています。
ここを間違えると、永遠にfailuresが返ってきます

```ruby:spec/models/user_speck
# 使われているメソッドについて共有
# valid?メソッド
# valid?メソッドを利用すると、ActiveRecord::Baseを継承しているクラスのインスタンスを保存する際に「バリデーションにより保存ができない状態であるか」を確かめることができます

# errorsメソッド
# valid？メソッドの返り値はtrue/falseですが、valid?メソッドを利用したインスタンス対してerrorsメソッドを利用すると、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができます。

# includeマッチャ
# includeマッチャは、引数にとった値がexpectの引数である配列に含まれているかをチェックすることができるマッチャです。
# 今回の場合、「nicknameが空の場合はcan't be blankというエラーが出るはずだ」ということがわかっているため、include("can't be blank")のように書くことができます。
#私は日本語化してしまっているので@message以降の値("can't be blank")に相当する部分は日本語で入力しています。
#ここを間違えると、永遠にfailuresが返ってきます。。。
#rails cをして@messages={:nickname=>["を入力してください"]}>←この[]内の記述をマッチャの後の（）に入れる
#実際にその通りになればこちらのエクスペクテーションはパスし、このコードは意図した動作をすると保証できます。

******************************************
# [nicknameが無い場合]

#require 'rails_helper'は必須
require 'rails_helper'
describe User do
  describe '#create' do
    it "nicknameないと登録できないよ" do
      #ユーザークラスのインスタンスを作成してます(userモデルでバリデーションかけてるカラムを入れてください↓)
      user = User.new(nickname: "", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000", first_name: "a", last_name: "a", birth_year: "2020", birth_month: "1", birth_day: "1", last_name_kana: "カナ", first_name_kana: "カナ")
      #バリデーションによって保存ができないようになっているか確認
      user.valid?
      # バリデーションによって生じたエラー内容詳細を返す記述
      # rails cで確認できる
      expect(user.errors[:nickname]).to include("を入力してください")
      #expect（ユーザークラスのニックネームカラム）←含まれているか（引数にとった値がexpextの引数である配列に含まれているか)
    end
  end
end
```

**[結果はこんな感じでかえってきます]**

```ruby:ターミナル
User
  #create
    nicknameないと登録できないよ

Finished in 0.22555 seconds (files took 4.38 seconds to load)
1 example, 0 failures
```

##6,もっと記述を簡単にするためのGemインストール
カラムが多いと１つずつカラムを足していくのが大変なので
Gemにfactorybotをインストールして
factorybotはカラムを自動生成してくれます

```ruby:Gemfile
group :development, :test do
  #省略
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end
```
`bundle`します

[基礎からやり直す Rails RSpec](https://qiita.com/kskinaba/items/38b143e79bdfd44cc42a)

##7,factorybotの準備
specディレクトリ直下に「factories」というディレクトリを追加し、その中に「users.rb」という名前でファイルを作成

users.rbを以下のように編集(チェックしたいからむの値を設定してます)

```ruby:spec/factories/users.rb
FactoryBot.define do

  factory :user do
    first_name            {"kkk"}
    first_name_kana       {"カタカナ"}
    last_name             {"kkk"}
    last_name_kana        {"カタカナ"}
    nickname              {"abe"}
    birth_year            {2020}
    birth_month           {1}
    birth_day             {1}
    email                 {"kkk@email.com"}
    password              {"00000000"}
    password_confirmation {"00000000"}
  end
end
```
これを書くとさっきの記述を下記のように置き換えられるようになります


```ruby:factory_botによるインスタンスの生成
#２つの記述は同じ意味合いを持つ

#factory_botを利用しない場合
user = User.new(nickname: "kkk", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000", first_name: "a", last_name: "a", birth_year: "2020", birth_month: "1", birth_day: "1", last_name_kana: "カナ", first_name_kana: "カナ")
#factory_botを利用する場合(:user以降はfactories/user.rbに書いた内容になる)
user = FactoryBot.build(:user)
```
＊buildメソッドはcreateメソッドに置き換えることもできます。
buildとほぼ同じ働きをしますが、createの場合はテスト用のDBに値が保存されます。

注意すべき点として、1回のテストが実行され、終了する毎にテスト用のDBの内容がロールバックされます。（保存された値がすべて消去されてしまう）
従って、binding.pry等でテストの実行を一時停止しないとテスト用のDBに保存された値をSequel Pro等で確認することはできません。

##8,factory_botの記法の省略

```ruby:spec/rails_helper.rb
#省略
RSpec.configure do |config|
  #下記の記述を追加
  config.include FactoryBot::Syntax::Methods

  #省略

end
```
**セットした値の上書き**

```ruby:
user = build(:user)
=><User:0x007fcabab94650
 id: nil,
#中略
nickname: "kkk">
user = build(:user, nickname: "ttt")
=><User:0x007fcac2a88998
 id: nil,
#中略
nickname: "ttt">
```

先ほどのspecファイルの記述をfactory_botを利用した形に変えます。

```ruby:models/user_spec.rb
require 'rails_helper'

describe User do
  describe '#create' do
    # 1. nicknameとemail、passwordとpassword_confirmationなどが存在すれば登録できること
    it "is valid with a nickname, email, password, password_confirmation" do
      user = build(:user)
      expect(user).to be_valid
    end

    # 2. nicknameが空では登録できないこと
    it "is invalid without a nickname" do
      user = build(:user, nickname: nil)
      user.valid?
      expect(user.errors[:nickname]).to include("を入力してください")
    end

    # 3. emailが空では登録できないこと
    it "is invalid without a email" do
      user = build(:user, email: nil)
      user.valid?
      expect(user.errors[:email]).to include("を入力してください")
    end

    # 4. first_nameが空では登録できないこと
    it "is invalid without a first_name" do
      user = build(:user, first_name: nil)
      user.valid?
      expect(user.errors[:first_name]).to include("を入力してください")
    end

    # 5. last_nameが空では登録できないこと
    it "is invalid without a last_name" do
      user = build(:user, last_name: nil)
      user.valid?
      expect(user.errors[:last_name]).to include("を入力してください")
    end

    # 6. birth_yearが空では登録できないこと
    it "is invalid without a birth_year" do
      user = build(:user, birth_year: nil)
      user.valid?
      expect(user.errors[:birth_year]).to include("を入力してください")
    end

    # 7. birth_yearが空では登録できないこと
    it "is invalid without a birth_month" do
      user = build(:user, birth_month: nil)
      user.valid?
      expect(user.errors[:birth_month]).to include("を入力してください")
    end

    # 8. birth_yearが空では登録できないこと
    it "is invalid without a birth_day" do
      user = build(:user, birth_day: nil)
      user.valid?
      expect(user.errors[:birth_day]).to include("を入力してください")
    end

    # 9. last_name_kanaが空では登録できないこと
    it "is invalid without a last_name_kana" do
      user = build(:user, last_name_kana: nil)
      user.valid?
      expect(user.errors[:last_name_kana]).to include("を入力してください")
    end

    # 10. first_name_kanaが空では登録できないこと
    it "is invalid without a first_name_kana" do
      user = build(:user, first_name_kana: nil)
      user.valid?
      expect(user.errors[:first_name_kana]).to include("を入力してください")
    end

    # 11. passwordが空では登録できないこと
    it "is invalid without a password" do
      user = build(:user, password: nil)
      user.valid?
      expect(user.errors[:password]).to include("を入力してください", "は7文字以上で入力してください", "は半角英数字７文字以上で入力してください")
    end

    # 12. passwordが存在してもpassword_confirmationが空では登録できないこと
    it "is invalid without a password_confirmation although with a password" do
      user = build(:user, password_confirmation: "")
      user.valid?
      expect(user.errors[:password_confirmation]).to include("とパスワードの入力が一致しません")
    end

    # 13. 重複したemailが存在する場合登録できないこと
    it "is invalid with a duplicate email address" do
      user = create(:user)
      another_user = build(:user, email: user.email)
      another_user.valid?
      expect(another_user.errors[:email]).to include("はすでに存在します")
    end

    # 14. passwordが7文字以上であれば登録できること
    it "is valid with a password that has more than 7 characters " do
      user = build(:user, password: "0000000", password_confirmation: "0000000")
      user.valid?
      expect(user).to be_valid
    end

    # 15. passwordが6文字以下であれば登録できないこと
    it "is invalid with a password that has less than 6 characters " do
      user = build(:user, password: "000000", password_confirmation: "000000")
      user.valid?
      expect(user.errors[:password]).to include("は7文字以上で入力してください")
    end
  end

  # 16. last_name_kanaがカタカナでないと登録できないこと
  describe '#katakana' do
    it 'last_name_kanaがカタカナで返ること' do
      user = build(:user, last_name_kana: "kana")
      user.valid?
      expect(user.errors[:last_name_kana]).to include("はカタカナで入力してください")
    end

    # 17. first_name_kanaがカタカナでないと登録できないこと    
    it 'first_name_kanaがカタカナで返ること' do
      user = build(:user, first_name_kana: "kana")
      user.valid?
      expect(user.errors[:first_name_kana]).to include("はカタカナで入力してください")
    end
  end
end
```
**テスト結果**

```ruby:
User
  #create
    is valid with a nickname, email, password, password_confirmation
    is invalid without a nickname
    is invalid without a email
    is invalid without a first_name
    is invalid without a last_name
    is invalid without a birth_year
    is invalid without a birth_month
    is invalid without a birth_day
    is invalid without a last_name_kana
    is invalid without a first_name_kana
    is invalid without a password
    is invalid without a password_confirmation although with a password
    is invalid with a duplicate email address
    is valid with a password that has more than 7 characters
    is invalid with a password that has less than 6 characters
  #katakana
    last_name_kanaがカタカナで返ること
    first_name_kanaがカタカナで返ること

Finished in 0.24017 seconds (files took 2.24 seconds to load)
17 examples, 0 failures
```

**でけた( ・∇・)**

**addressモデルのテスト**
1/14更新
都道府県はmodelにenumで記述をしているので、今回は数字で作成しています。
もしenumなど使っていない場合は記述を変えてください。

```ruby:spec/factories/addresses
FactoryBot.define do
  factory :address do
    postal_code { "111-1111" }
    prefectures { 1 }
    city { "豊島区" }
    address { "三角町1-1-1" }
  end
end
```

```ruby:spec/models/address_spec.rb
require 'rails_helper'

describe Address do
  describe '#create' do
    #1,prefectures,city, address, postal_codeが存在すれば登録できること
    it "is valid with a prefectures,city, address, postal_code" do
      address = build(:address)
      expect(address).to be_valid
  end

    # 2. prefecturesが空では登録できないこと
    it "is invalid without a prefectures" do
      address = build(:address, prefectures: nil)
      address.valid?
      expect(address.errors[:prefectures]).to include("を入力してください")
    end

    # 3. cityが空では登録できないこと
    it "is invalid without a city" do
      address = build(:address, city: nil)
      address.valid?
      expect(address.errors[:city]).to include("を入力してください")
    end

    # 4. addressが空では登録できないこと
    it "is invalid without a address" do
      address = build(:address, address: nil)
      address.valid?
      expect(address.errors[:address]).to include("を入力してください")
    end
    
    #5. postal_codeが空では登録できないこと
    it "is invalid without a postal_code" do
      address = build(:address, postal_code: nil)
      address.valid?
      expect(address.errors[:postal_code]).to include("を入力してください")
    end

    # 6. postal_codeがハイフンあり7桁であれば登録できること
    it "is valid with a postal_code 3桁-４桁 " do
      address = build(:address, postal_code: "1234567")
      address.valid?
      expect(address.errors[:postal_code]).to include("はハイフンを入れて半角英数字で入力してください")
    end
  end
end
```

##9,終わりに
カタカナのテストコードに悩んですごく時間をかけてしまいましたが
そもそも勘違いが原因。
factorybotでカタカナの記述を行い、バリデーションをすでに組んでいるので
テストコードではカタカナ以外の値を入れてエラーになるかどうかを見れば良いだけでした。
一度、テストコード全て消してやり直したら案外あっさり行きました。
発想の転換て大事ですね。

でも悩みまくったおかげで面白いGemを発見したので参考までに
下記に記述残しておきます。
他のモデルのバリデーション用テストコードも順次アップします。

[【Rails】まだValidatorのテストで消耗してるの？](https://qiita.com/izumin5210/items/a7036093c25c42f2298d)
[rspec-validator_spec_helper](https://github.com/izumin5210/rspec-validator_spec_helper)
[全角カタカナにのみマッチする正規表現](https://qiita.com/necojackarc/items/cad2d4eb80f0629ad196)
[【RSpec】letはbefore内では使えない](https://qiita.com/sasakura_870/items/3cdad3e0407677a536c5)
[ランダムな日本語のデータを生成するGemまとめ](https://qiita.com/Peranikov/items/12f1015fc20fa41844b3)