RSpecまとめ

RSpecについて、知識の定着がまだまだなのでまとめがてら復習しておきたいと思います

#RSpecとは
ソフトウェアテストの一種でプログラムが想定通り動くかテストする。テストしたい項目の単位を「テストケース」という。

ログイン機能を例とすると、

　ログインできる＝正しいパスワード
　ログインできない＝正しくないパスワード
となる。

##RSpecをインストールする

| 名前 | 概要 |
|:-:|:-:|
|  rspec-rails	 |  Rails向けのRSpec　テストのフレームワーク |
|  factory_bot_rails	 |  自動テストで使うテストデータの用意が簡単になる |

```
Gemfile

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

    :
  (省略)
    :

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]

  # ==========追加する==========
  gem 'rspec-rails', '~> 3.8'
  gem 'factory_bot_rails', '~> 5.0'


end

group :development do

    :
  (省略)
    :

end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]


↓

bash
$ bin/bundle install

```

##テストの種類

###手動テスト
人がテストケースを読み、確認してテストすることを「手動テスト」という。

これといった正解のない、レイアウトやアニメーションの確認をするときに有効。ただし、工数が多くなりやすい、テストの操作を間違えやすいというデメリットもある。

###自動テスト

テストケースをプログラムに書いて実行する。簡単、高速、確実だが、既存機能の仕様変更の際、テストコードの修正も必要になる。


##テストレベルとは
テストは開発工程ごとで範囲や細かさが違います。WEBアプリケーションの開発では主に「単体テスト」「結合テスト」「システムテスト」がある。


###単体テスト
その名の通り、小さな範囲をテストする。

モデルの場合、データを正しく参照できるか、登録できているか
メソッドの場合、与えた引数に対して正しい戻り値が返ってくるか
をテスト。

```
※例
require 'rails_helper'

RSpec.describe FoodEnquete, type: :model do
  describe '正常系の機能' do
    context '回答する' do
      it '正しく登録できること 料理:やきそば food_id: 2, 
                            満足度:良い score: 3, 
                            希望するプレゼント:ビール飲み放題 present_id: 1)' do

        enquete = FoodEnquete.new(
          name: '田中 太郎',
          mail: 'taro.tanaka@example.com',
          age: 25,
          food_id: 2,
          score: 3,
          request: 'おいしかったです。',
          present_id: 1
        )

        expect(enquete).to be_valid

        enquete.save

        answered_enquete = FoodEnquete.find(1);

        expect(answered_enquete.name).to eq('田中 太郎')
        expect(answered_enquete.mail).to eq('taro.tanaka@example.com')
        expect(answered_enquete.age).to eq(25)
        expect(answered_enquete.food_id).to eq(2)
        expect(answered_enquete.score).to eq(3)
        expect(answered_enquete.request).to eq('おいしかったです。')
        expect(answered_enquete.present_id).to eq(1)

      end
    end
  end
end

```

| キーワード| 命名 | 意味 |
|:-:|:-:|:-:|
| describe |  正常系の機能 | 大分類　何についてテストをするか  |
|  context | 回答する  | 中分類　分類が少ないときは省略できる  |
| it  | 正しく登録できること...  |  個々のテストケースを表し、期待する振舞いを記述 |


####RSpecの記法

```
※バリデーションが通ること(バリデーションエラーが無いこと)

expect(enquete).to be_valid


※answered_enquete.nameが田中 太郎であること

expect(answered_enquete.name).to eq('田中 太郎')
```

| 項目 | 意味 |
|:-:|:-:|
| expect  |  テストしたいものを指定　テストデータenqueteを指定 |
| to  |  「であること」を意味する |
|  be_valid |  テストデータenqueteのバリデーションが通ること |

この中でbe_validは検証の種類である「マッチャー」に分類される。マッチャーにはeqやlncludeなどがある。

```
実行

$ bundle exec rspec 〇〇（ファイル名）

F

Failures:

  1) FoodEnquete 正常系の機能 回答する 正しく登録できること 料理:やきそば food_id: 2, 
                            満足度:良い score: 3, 
                            希望するプレゼント:ビール飲み放題 present_id: 1)
    Failure/Error: expect(answered_enquete.name).to eq('中田 太郎')
    
      expected: "中田 太郎"
           got: "田中 太郎"
    
      (compared using ==)
    # ./spec/models/food_enquete_spec.rb:23:in `block (4 levels) in <top (required)>'

Finished in 0.02235 seconds (files took 0.83042 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/food_enquete_spec.rb:6 # FoodEnquete 正常系の機能 回答する 正しく登録できること 料理:やきそば food_id: 2, 
                            満足度:良い score: 3, 
                            希望するプレゼント:ビール飲み放題 present_id: 1)
```

####FactoryBot

テストデータを呼び出せるようにする

```

       enquete_tanaka = FoodEnquete.new(
         name: '田中 太郎',
         mail: 'taro.tanaka@example.com',
         age: 25,
         food_id: 2,
         score: 3,
         request: 'おいしかったです。',
         present_id: 1
       )
       enquete_tanaka.save

```

↓テストデータを置き換える↓

```
FactoryBot.create(:food_enquete)
```

FactoryBot用のファイルを作り、:food_enquete（Factory名）からテストデータを呼び出せる

```

FactoryBot.define do
  factory :food_enquete do
    name { '田中 太郎' }
    mail { 'taro.tanaka@example.com' }
    age { 25 }
    food_id { 2 }
    score { 3 }
    request { 'おいしかったです。' }
    present_id { 1 }
  end
end

```

####共通化

before 前処理

```
describe 'アンケート回答時の条件' do
  context 'メールアドレスを確認すること' do
    it '同じメールアドレスで再び回答できないこと' do
 
      # FactoryBot.create(:food_enquete_tanaka)

      re_enquete_tanaka = FactoryBot.build(:food_enquete_tanaka, food_id: 0, score: 1, present_id: 0, request: "スープがぬるかった")
      expect(re_enquete_tanaka).not_to be_valid
      expect(re_enquete_tanaka.errors[:mail]).to include(I18n.t('errors.messages.taken'))
      expect(re_enquete_tanaka.save).to be_falsey
      expect(FoodEnquete.all.size).to eq 1
    end

    it '異なるメールアドレスで回答できること' do
 
      # FactoryBot.create(:food_enquete_tanaka)

      enquete_yamada = FactoryBot.build(:food_enquete_yamada)

      expect(enquete_yamada).to be_valid
      enquete_yamada.save
      expect(FoodEnquete.all.size).to eq 2
    end
  end

```

.
.

```
#同じテストデータを呼び出す処理
FactoryBot.create(:food_enquete_tanaka)

↓

# 前処理を共通化してテストデータを作成。  
before do
  FactoryBot.create(:food_enquete_tanaka)
end
```

.
.

```
describe 'アンケート回答時の条件' do
  context 'メールアドレスを確認すること' do


    # 前処理を共通化してテストデータを作成。  
    before do
      FactoryBot.create(:food_enquete_tanaka)
    end


    it '同じメールアドレスで再び回答できないこと' do

　　　　＃まずはbeforeを実行してくれる

      re_enquete_tanaka = FactoryBot.build(:food_enquete_tanaka, food_id: 0, score: 1, present_id: 0, request: "スープがぬるかった")
      expect(re_enquete_tanaka).not_to be_valid
      expect(re_enquete_tanaka.errors[:mail]).to include(I18n.t('errors.messages.taken'))
      expect(re_enquete_tanaka.save).to be_falsey
      expect(FoodEnquete.all.size).to eq 1
    end

    it '異なるメールアドレスで回答できること' do

　　　＃まずはbeforeを実行してくれる

      enquete_yamada = FactoryBot.build(:food_enquete_yamada)

      expect(enquete_yamada).to be_valid
      enquete_yamada.save
      expect(FoodEnquete.all.size).to eq 2
    end
  end
```

他にも、このような形で
インスタンス

```
new_enquete = FoodEnquete.new
↓
let(:new_enquete) { FoodEnquete.new }
```

共通メソッド

```
＃共通化したいメソッド
shared_examples '満足度の表示' do
  let(:object_name) { described_class.to_s.underscore.to_sym }
  let(:model) { FactoryBot.build(object_name) }

  it '満足度が「悪い」になること' do
    model.score = 1
    expect(model.view_score).to eq I18n.t('common.score.bad')
  end

  it '満足度が「普通」になること' do
    model.score = 2
    expect(model.view_score).to eq I18n.t('common.score.normal')
  end
.
. 　
.
  end

↓

  describe '共通メソッド' do
    it_behaves_like '満足度の表示'
  end
```

共通バリデーション

```
shared_examples '入力項目の有無' do
  let(:object_name) { described_class.to_s.underscore.to_sym }
  let(:model) { FactoryBot.build(object_name) }

  context '必須入力であること' do
    it 'お名前が必須であること' do
      expect(model).not_to be_valid
      expect(model.errors[:name]).to include(I18n.t('errors.messages.blank'))
    end

.
.
end
.
.
.
end

↓

  describe '共通バリデーション' do
    it_behaves_like '入力項目の有無'
    it_behaves_like 'メールアドレスの形式'    
  end


```

など、共通化できるものが数多くある

###結合テスト
必要なGem

| 名前 | 概要 |
|:-:|:-:|
| capybara	  |  結合テスト(E2E)のフレームワーク。自動操作を支援する。 |
|  selenium-webdriver	 |  capybaraを通してWEBブラフザを自動で操作。 |
| webdrivers  |  Google Chromeを操作するドライバ |


```
#Gemfile

.
.
# 追加する
group :test do
  gem 'capybara', '~> 3.28'
  gem 'selenium-webdriver', '~> 3.142'
  gem 'webdrivers', '~> 4.1'
end

↓
bash
$ bin/bundle install
```

spec/spec_helper.rbのコードを修正

```
＃Capybaraを参照
require 'capybara/rspec'

＃ブラウザにChromeを指定
  config.before(:each, type: :system) do
    driven_by :selenium, using: :chrome, screen_size: [1280, 960]
  end
```

.
.
結合テスト用のファイルを作成

```
require 'rails_helper'

RSpec.describe "FoodEnquetes", type: :system do
  describe '正常' do
    context "アンケートに回答する" do
      it "回答できること" do
        
        enquete = FactoryBot.create(:food_enquete_tanaka)
        visit "/food_enquetes/new"
        fill_in 'お名前', with: enquete.name
        fill_in 'メールアドレス', with: enquete.mail
        fill_in '年齢', with: enquete.age
        select enquete.food_name, from: 'お召し上がりになった料理'
        choose "food_enquete_score_#{enquete.score}"
        fill_in 'ご意見・ご要望', with: enquete.request
        select enquete.present_name, from: 'ご希望のプレゼント'

        sleep 2
        click_button '登録する'

        expect(page).to have_content 'ご回答ありがとうございました'
        expect(page).to have_content 'お名前: 田中 太郎'
        expect(page).to have_content 'メールアドレス: taro.tanaka@example.com'
        expect(page).to have_content '年齢: 25'
        expect(page).to have_content 'お召し上がりになった料理: やきそば'
        expect(page).to have_content '満足度: 良い'
        expect(page).to have_content 'ご意見・ご要望: おいしかったです。'
        expect(page).to have_content 'ご希望のプレゼント: 【10名に当たる】ビール飲み放題'


      end
    end
  end
end

↓
bash
$ bundle exec rspec 〇〇ファイル名

```

すると、自動的にChromeが起動してアンケートの回答をしてくれる。