テストコードを書いてみよう

つぶやきアプリを使ってテストコードを書いてみましょう。

##テストコード
アプリケーション内に記述する、そのアプリケーションの挙動を確認するためのコードのことです。

##RSpec
Ruby on Railsのテストコードを書くために用いられるGemです

アプリケーションの挙動を確認するときは、「うまくいくとき」「うまくいかないとき」をそれぞれ確認する必要があります。
それぞれ正常系と異常系と言います。

##正常系
「ユーザーが開発者の意図する操作を行った時の挙動」を確認するテストコードが、正常系に分類されます。
新規登録する時に必須項目を入力したら会員登録ができるなどです

##異常系
「ユーザーが開発者の意図しない操作を行った時の挙動」を確認するテストコードが、異常系に分類されます。
たとえば、必須項目を入力せずに送信した時にエラー文を表示するなどです。

テストコードの種類は、大きく2つあります。部分的に意図通りに動作するか確認する単体テストと一連の動作が問題なく行われるか流れも確認する結合テストです。

##単体テストコード
モデルやコントローラーなどの機能ごとに問題がないか確かめます。たとえば「投稿する時にテキストが何もない場合はテーブルに保存させない」というバリデーションの挙動を確認します。

##結合テストコード
ユーザーがブラウザで操作する一連の流れを再現して、問題がないか確かめます。例えばトップページから新規登録画面に遷移し、名前やメールアドレス、パスワードを入力して登録ボタンを押して登録できたらトップページに戻るという流れを一気に確かめます。

##単体テストコードから書いていきましょう。

RSpecのGemをに導入します。

```rb:Gemfile
group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 4.0.0'
end
```

group :development, :testというグループの中に記述しましょう。
そうすることで、Gemの動作に制限をもたせます。

Gemをインストールします。

```
% bundle install
```

アプリケーション内でRSpecを使用するための設定を行います。

```
% rails g rspec:install
```

Userモデルのテストファイルを生成します。

```
% rails g rspec:model user
```

```spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end
```
RSpecでモデル、ビュー、コントローラーのテストを行うためには、rails_helper.rbというファイルを読み込む必要があります。

##rails_helper
Rspecを用いてRailsの機能をテストするときに、共通の設定を書いておくファイルです。各テスト用ファイルでspec/rails_helper.rbを読み込むことで、共通の設定やメソッドを適用します。


まずは、これから実装するテストコードで「どの機能に対してのテスト行うか」を明記します。
この場合、RSpecにおいてはdescribeメソッドを使用します。

##describe
テストコードのグループ分けを行うメソッドです。
「どの機能に対してのテストを行うか」をグループ分けし、各テストコードを記述します。

do~endの間に、さらにdescribeメソッドを記述することで、入れ子構造をとれます。

「ユーザー新規登録」についてのテストを行うことを記述しましょう。

```rb:spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    # この中にテストコードを記述していきます。
  end
end
```
もう少し詳細に分けて記述していきます。

##it

itメソッドは、describeメソッド同様に、グループ分けを行うメソッドでより詳細に
「describeメソッドに記述した機能において、どのような状況のテストを行うか」を書いていきます。
itメソッドで分けたグループを、exampleとも呼びます

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

RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空では登録できない' do
      # この中にテストコードを記述します
    end
    it 'emailが空では登録できない' do
      # この中にテストコードを記述します
    end
  end
end
```

一度テストコードを実行してみましょう。
実行するときには、bundle execコマンドに続けて、rspecコマンドを入力して実行します。

 ##bundle execコマンド
Gemの依存関係を整理してくれるコマンドです。
多くのGemはその他のGemと関係があり、互いに依存しています。
bundle execコマンドを用いてGemの依存関係を整理する必要があります。

テストコードを実行してみます。

```
% bundle exec rspec spec/models/user_spec.rb 
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1008745/006ace9e-766c-89c0-fe0d-a2ef567b9006.png)


結果が緑色で表示されれば実行成功です。


それでは実際にnicknameが空の場合の記述をしていきましょう。

異常系のモデル単体テストの実装は、以下の流れで進みます。

①保存するデータ（インスタンス）を作成する
②作成したデータ（インスタンス）が、保存されるかどうかを確認する
③保存されない場合、生成されるエラーメッセージが想定されるものかどうかを確認する

nicknameのバリデーションに指定されている、presence: trueの挙動を検証します。
空の値を指定して、インスタンスを生成しましょう。

```rb:spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空だと登録できない' do
      user = User.new(nickname: '', email: 'test@example', password: '123456', password_confirmation: '123456')
    end
    it 'emailが空では登録できない' do
    end
  end
end
```

nicknameに設定されているpresence: tureが正常に機能するか検証するため、バリデーションを実行します。バリデーションはDBに保存する前しか実行されません。
valid?メソッドを用いて、任意のタイミングでバリデーションを実行しましょう。

##valid?
valid?は、バリデーションを実行させて、エラーがあるかどうかを判断するメソッドです。
エラーがない場合はtrueを、ある場合はfalseを返します。
また、エラーがあると判断された場合は、エラーの内容を示すエラーメッセージを生成します。

コンソールで確認してみましょう。

```
% rails c
[1] pry(main)> user = User.new(nickname: '')
[2] pry(main)> user.valid?
=> false
```

実際にバリデーションを実行してみましょう。

```rb:spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空だと登録できない' do
      user = User.new(nickname: '', email: 'test@example', password: '123456', password_confirmation: '123456')
      user.valid?
    end
    it 'emailが空では登録できない' do
    end
  end
end
```

Userモデルのバリデーションにはpresence: trueが指定されているため、nicknameが空ではDBに保存できないと判断され、valid?メソッドはfalseを返すはずです。

上記のような「想定した挙動」と「アプリの実際の挙動」を確認します。
この確認をテストコードに落とし込んだものをexpectationといいます。

##expectation
検証で得られた挙動が想定通りなのかを確認する構文のことです。
expect().to matcher()を雛形に、テストの内容に応じて引数やmatcherを変えて記述します。

##matcher（マッチャ）
「expectの引数」と「想定した挙動」が一致しているかどうかを判断します。
expectの引数には検証で得られた実際の挙動を指定し、マッチャには、どのような挙動を想定しているかを記述します。

代表的な2つのincludeとeqマッチャを紹介します。


##include
includeは、「expectの引数」に「includeの引数」が含まれていることを確認するマッチャです。

具体的には、以下のように記述します。

```rb:【例】includeマッチャのテストコード
describe '寿司のネタ' do
  it '寿司のネタにカツオが含まれている' do
     expect(['イカ', '大トロ', 'カツオ', 'うに']).to include('カツオ')
  end
end
```

イカ、大トロ、カツオ、うにが入った配列に、カツオが含まれることを想定しています。
このテストコードを実行した場合、想定通り配列のなかにカツオは含まれているのでテストは成功します。

##eq
eqは、「expectの引数」と「eqの引数」が等しいことを確認するマッチャです。

```rb:【例】eqマッチャのテストコード
describe '足し算' do
  it '2 + 3の計算結果は5と等しい' do
     expect(2 + 3).to eq(5)
  end
end
```

このテストコードを実行した場合、想定通りの値になるためテストは成功します。


今回行っているテストのエクスペクテーションを考えましょう。

Userモデルで、nicknameにはpresence: trueのバリデーションを設けています。

このバリデーションが正しく機能していれば、valid?メソッドを実行したときにfalseが返ってくるはずです。falseが返ってくるということはDBに保存できないと判断されたため、インスタンスにはエラーの内容を示す情報が生成されます。

errorsメソッドを用いてエラーの内容を確認してみます。
コンソールで確認していきましょう。

```console:
% rails c
[1] pry(main)> user = User.new(nickname: '', mail: 'test@example', password: '123456', password_confirmation: '123456')
[2] pry(main)> user.valid?
User Exists? (10.4ms)  SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'test@example' LIMIT 1
=> false
[3] pry(main)> user.errors
=> #<ActiveModel::Errors:0x00007feb3e0120a8
 @base=
  #<User id: nil, email: "test@example", created_at: nil, updated_at: nil, nickname: "">,
 @details={:nickname=>[{:error=>:blank}]},
 @messages={:nickname=>["can't be blank"]}>
```

nicknameでblank（空である）というエラーが起こっていますね。

この複雑なエラーの情報は、expectの中には記載できませんのでここからエラー内容を絞って取り出しましょう。


## full_messages
full_messagesは、エラーの内容から、エラーメッセージを配列として取り出すメソッドです。

ターミナルでuser.errors.full_messagesと実行すると、以下のようにエラーの内容の配列が返されます。
続けてコンソールに入力していきましょう！

```console:
[4] pry(main)> user.errors.full_messages
=> ["Nickname can't be blank"]
```

expectの引数にはvalid?メソッドを使用した後のエラーメッセージを指定しましょう。
expect(user.errors.full_messages)となります。

full_messagesの返り値は配列であるため、includeマッチャを用いて、配列にどのようなエラーが含まれていればよいか指定します。nicknameでpresence: trueによるエラーが起こるはずであるため、想定するエラーメッセージは"Nickname can't be blank"になります。

```rb:spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空では登録できない' do
      user = User.new(nickname: '', email: 'test@example', password: '000000', password_confirmation: '000000')
      user.valid?
      expect(user.errors.full_messages).to include("Nickname can't be blank")
    end
    it 'emailが空では登録できない' do
    end
  end
end
```

テストコードを実行しましょう。

```console:
% bundle exec rspec spec/models/user_spec.rb 
```

次はemailが空だと登録できないについてテストしましょう。
今度はbinding.pryを使って処理を途中で止めてみます。

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

RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空では登録できない' do
      user = User.new(nickname: '', email: 'test@example', password: '123456', password_confirmation: '123456')
      user.valid?
      expect(user.errors.full_messages).to include("Nickname can't be blank")
    end
    it 'emailが空では登録できない' do
      user = User.new(nickname: 'test', email: '', password: '123456', password_confirmation: '123456')
      user.valid?
      binding.pry
    end
  end
end
```

テストコードを実行してみます。

```
% bundle exec rspec spec/models/user_spec.rb 
```

途中で処理が止まるので以下の項目を入力していきましょう。

```
[1] pry(main)> user.errors
=> #<ActiveModel::Errors:0x00007fba4d3e0a80
 @base=#<User id: nil, email: "", created_at: nil, updated_at: nil, nickname: "test">,
 @details={:email=>[{:error=>:blank}]},
 @messages={:email=>["can't be blank"]}>
[2] pry(main)> user.errors.full_messages
=> ["Email can't be blank"]
```

エラーメッセージの内容がわかったので記述しましょう。

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

RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    it 'nicknameが空では登録できない' do
      user = User.new(nickname: '', email: 'test@example', password: '123456', password_confirmation: '123456')
      user.valid?
      expect(user.errors.full_messages).to include("Nickname can't be blank")
    end
    it 'emailが空では登録できない' do
      user = User.new(nickname: 'test', email: '', password: '123456', password_confirmation: '123456')
      user.valid?
      expect(user.errors.full_messages).to include("Email can't be blank")
    end
  end
```
テストコードを実行します。

```
% bundle exec rspec spec/models/user_spec.rb 
```

緑色になっていれば成功です。
本来はまだ細かくテスト項目があるのですが今回はここまでとします。