コードの信頼を高めよう! RSpecによるRailsテスト入門

## RSpecとは
　Rubyプログラムやアプリケーションを自動的にテストするための振る舞い駆動型開発(BDD)フレームワーク。
#### BDDとは
　開発の前に仕様や要件を定義し、それを基にテストを開発者以外がみても理解できるように自然言語に近い形式で記述する。
## テストを書く意味
- アプリケーションの品質担保
- システム要件の理解度向上
- プログラムのバグ削減
- 不具合再発防止
- 後々リファクタリングしやすくするため。
「テストコードがない状態だとリファクタリングできない。きれいなコードを書くなら、まずテストコードから」
## テストを書くときに意識すること
- DRYであること(Don't Repeat Yourself:同じことを繰り返すな)
- 信頼できるものであること
- 誰が見ても理解できること。将来の人のためにも。
- テストコードのコーディングルールを厳しくしすぎない。
## モデルスペックテスト
### validationテスト

```ruby
RSpec.describe User, type: model do
  describe 'validation' do
#正常系
    it "is valid when firstname, lastname, email, and password are present" do 
      user = User.new(
        firstname: 'John',
        lastname: 'Williams',
        email: 'test@example.com',
        password: 'D3keL1fS'
      )
      expect(user).to be_valid
    end
#異常系
    it "is invalid when firstname is missing" do 
      user = User.new(
        firstname: nil,
        lastname: "Williams",
        email: "test@example.com",
        password: 'D3keL1fS'
      )
      user.valid?
      expect(user.errors[:firstname]).to include("can't be blank")
    end
  end
end
```
- 　`describe`で「この機能のテストをするよ。」とテストの構造をグルーピングしている。このケースではvalidationをチェックするもの。
- 　 `it`はテストをexample（ it で始まる1行）単位にまとめている。itの後には期待する内容を記述する。このケースでは`"firstname, lastnaem, email, passwordがあるとき`と`firstnameがないとき`の2つのテストを記述。
- 　`expect`は期待する結果。
  -  正常系は`expect(contact).to be_valid　`は`user`が`be_valid　`の時。つまりvalid(値が存在している）を期待する。
  - 　異常系は`valid?`でfalseだったらエラーメッセージが`"can't be blank"`という文字列を含んでいることを期待する。

　以下の設定がされている場合、firstnameが空白（nilや空の文字列）であると"can't be blank"というエラーメッセージが`user.errors[:firstname]`に追加される。
```models/user.rb
class User < ApplicationRecord
  validates :firstname, presence: true
  # 他のバリデーションや設定
end
```
### 上のテストコードをリファクタリングする
#### beforeで共通化
　DRY原則にしたがってit内で同じコードがあるので共通化をする。重複しているUseオブジェクト生成を一つにまとめてこれをインスタンス変数として定義する。
```ruby
user = User.new(
        firstname: "John",
        lastname: "Williams"
        email: "test@example.com",
        password: 'D3keL1fS'
    )
```
#### contextでまとめる
　各ブロックで共通している処理があればbeforeで書くのが推奨される。機能をグループ化する点では`describe `も`context`も同じだが、`context`は条件を分けるときにつかう。より整理されコードが見やすくなる。
```ruby
RSpec.describe User, type: :model do
  describe 'validation' do
    before :each do
      @user = User.new(
        firstname: "John",
        lastname: "Williams",
        email: "test@example.com",
        password: 'D3keL1fS'
    )

    context 'when firstname, lastname, email, and password are present' do
      it 'is valid' do
        expect(@user).to be_valid
      end
    end

    context 'when firstname is missing' do
      it 'is invalid' do
        @user.firstname = nil
        @user.valid?
        expect(@user.errors[:firstname]).to include("can't be blank")
      end
    end
  end
end
```
　`describe`は親、`context`は子。つまり親の中でbeforeで定義した`@user`は子のcontext内で使える。
### beforeとlet
　`before` ブロックを使うと `describe` や `context` ブロックの内部で、各テストの実行前に共通のインスタンス変数をセットアップできる。しかし問題点が二つある。
##### 1. before の中に書いたコードは describe や context の内部に書いたテストを実行するたびに 毎回 実行される。これはテストに予期しない影響を及ぼす恐れがある。使う必要のないデータを作成してテストを遅くする原因となる。
##### 2.要件が増えるにつれてテストの可読性を悪くする。
　こうした問題に対処するため、RSpecは `let` というメソッドを提供している。`let`は遅延評価であり、必要になった時に使われる。
```ruby
RSpec.describe User, type: :model do
  describe 'validation' do
    let(:user) do
      User.new(
        firstname: "John",
        lastname: "Williams",
        email: "test@example.com",
        password: 'D3keL1fS'
      )
     end

     context 'when firstname, lastname, email, and password are present' do
       it 'is valid' do
         expect(user).to be_valid
       end
     end

     context 'when firstname is missing' do
       it 'is invalid' do
        user.firstname = nil
        user.valid?
        expect(user.errors[:firstname]).to include("can't be blank")
       end
     end
  end
end
```
`　before`より`let` の方が可読性がよくなる。
### Factory Botでテストデータを作成
　RSpecと組み合わせて使われることが多いテストデータの生成ライブラリ。Factory Botを使用することで、テストケースごとに必要なデータを「工場（Factory）」と呼ばれるテンプレートから簡単に生成できる。
#### ファクトリの定義
```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    firstname { "John" }
    lastname { "Williams" }
    email { "test@example.com" }
    password { 'D3keL1fS' }
  end
end
```
#### テスト内での使用
　`　FactoryBot.build(:user)`と書くだけで、簡単にインスタンス化できる。テストデータを作れば、そのユーザーの名前は毎回基本的に John Williams になる。メールアドレスもパスワードも最初から設定された状態になる。
```spec/models/user_spec.rb
RSpec.describe User, type: :model do
  let(:user) { FactoryBot.build(:user) }

  it "is valid with a firstname, lastname, email, and password " do 
    expect(user).to be_valid
  end
  it "is not valid without a firstname" do 
    user.firstname = nil
    user.valid?
    expect(user.errors[:firstname]).to include("can't be blank")
  end
end
```
### validationテストはするべきか
　必ずしもこのような単純なvalidationの時はテストの量が膨大になるので書く必要はない。
```ruby
validates :firstname, presence: true
```
　しかし、複雑な正規表現、カスタムバリデータ、ややこしい条件、お金が発生する重要な場面ではテストは必要だと考える。以下はemailのフォーマットをチェックしている。
```ruby
validates :email, presence: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i/ }
```
## System Spec
### Capybaraでシュミレーションテスト
　ページの読み込み、フォームの入力・送信、リンクやボタンのクリックなど　アプリケーションを実際に動かしているかのように動作テストができる。
　[Capybara DSLのドキュメント](https://github.com/teamcapybara/capybara#the-dsl)にはそれ以外にも便利なメソッドがある。
|メソッド  |操作  |
|---|---|
|visit  |指定したページに遷移  |
|click_link  | 指定したリンク文字列を持つaタグをクリック  |
|fill_in  | 入力したい文字列を指定のフォームに入力  |
|click_button  | 指定したラベルを持つボタンをクリック  |
#### ログインし、プロジェクト作成するテスト
```ruby
 require 'rails_helper'

RSpec.describe "Projects", type: :system do
  before do
    driven_by(:rack_test)
  end

  scenario "user creates a new project" do  #itと同じexampleの起点
   #最初に新しいテストユーザーを作成
    user = FactoryBot.create(:user)

    visit root_path
    #ログイン画面から作成したユーザーでログイン
    click_link "Sign in"
    #フォームにemailとpasswordを入力
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    #ログインボタンをクリックしてログイン
    click_button "Log in"

    #ユーザーは新しいプロジェクトを作成する
    expect {
      #新しくプロジェクト作成する
      click_link "New Project"
      #フォームにプロジェクト名と説明文を入力
      fill_in "Name", with: "Test Project"
      fill_in "Description", with: "Trying out Capybara"
      click_button "Create Project"
      expect(page).to have_content "Project was successfully created"
      expect(page).to have_content "Test Project"
      #プロジェクトのオーナー名を作成したユーザー名と保有するプロジェクトが+1したものを表示されているか確認
      expect(page).to have_content "Owner: #{user.name}"
    }.to change(user.projects, :count).by(1)
  end
```
- 　`expect{}` の中ではブラウザ上でテストしたいステップを明示的に記述し、それから、結果の表示が期待どおりになっていることを検証している。
- 　`have_content`で特定のテキストがページ上に表示されているか確認できる。
### テスト駆動開発(TDD)。レッドからグリーンへ
　１０章までで基礎知識を勉強したあと、11章でテスト駆動開発を行う。スペックを実行してエラー文を確認し、コードを書き加えて赤（エラー状態）」から「緑（成功状態）」に遷移するプロセスを体験できる。

## 参考書籍

https://leanpub.com/everydayrailsrspec-jp