RSpecのディレクトリ構成とspecごと役割について

#はじめに
　現在作成中のWebアプリケーションのテストフレームワークについて、[前回作成した記事](https://qiita.com/shogo-1988/items/3114420b4ead7076e2c7)を考慮して、MinitestからRSpecへ移行しています。その際、Minitestではあまり意識していなかったテストの種類について、意識することが必要だと感じました。そのテストがUIのテストなのか、コントローラーのテストなのか、モデルのテストなのか、それによってテストの書き方が異なります。本記事ではRSpecのディレクトリ構成とテストの種類ごとのコードの書き方について簡単にまとめました。
　　参考にしました→[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)
　　　　　　　　　　[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)

##RSpecの主なディレクトリ
RSpecのテストの種類をSpecと呼びます。Specは通常、その目的を説明する正規のディレクトリ構造に配置されます。

**【主なRSpecディレクトリ構成】**

 spec ー  ー  ー models
　　　   　｜ 
　　　   　｜ー requests
　　　   　｜
　　　   　｜ー features
　　　   　｜
　　　   　｜ー helpers
　　　   　｜
　　　   　｜ー mailers
　　　   　｜
　　　   　｜ー system

##主なSpecの役割について
ここでは、**Model Spec**、**Request Spec**、**Feature Spec**、**System Spec**の役割について紹介します

###Model Spec
Model Specはバリデーション等のRailsのモデルのテストします。
下記のサンプルコードで確認していきます。

```Ruby:spec/models/user_spec.rb
RSpec.describe User, type: :model do

  describe 'ユーザー名' do
    it 'ブランクの場合、無効な状態であること' do
      user = User.new( name: '  ',
                       email: 'user@example.com',
                       password: 'password',
                       password_confirmation: 'password')
      expect(user).to_not be_valid
    end

    it '長過ぎる場合、無効な状態であること' do
      user = User.new( name: 'a' * 51,
                       email: 'user@example.com',
                       password: 'password',
                       password_confirmation: 'password')
      expect(user).to_not be_valid
    end
  end
end
```
一つ一つ内容を確認していきます。

・``type: :model``でspecを指定します。ここではユーザーモデルをテストしています。

・``describe``は期待する結果をグループ化しています。
「ユーザーモデルのテストをする」こととその中で「ユーザー名のテストをする」ことを宣言しています。

・``User.new``ではユーザーを作成しています。

・``it``は**example**という``describe``より小さい単位でまとめています。
　``it``内に複数のエクスペクテーションを記載できます。しかし、途中で失敗した場合、その先は流れない為、基本的に``it``一つに一つのエクスペクテーションが推奨されます。

・``expect``ではエクスペクテーション(expectation、期待する内容)を記載しています。
　``expect(user).to_not be_valid``は「userが有効ではないか？」ということをテストしています。
　英文のようにテストを記載できるのがRSpecの利点の一つですね。
　``to_not be_valid``の部分は**マッチャ(matcher)**と呼ばれ様々な種類があります。
　その他のマッチャについては[こちら](https://relishapp.com/rspec/rspec-rails/v/4-0/docs/matchers)に記載があります。

###Request Spec
Request Specは[公式ドキュメント](https://relishapp.com/rspec/rspec-rails/v/4-0/docs/request-specs/request-spec)よると、「ルーティングを含むフルスタックを介して、スタブ[^1]なしで動作を駆動するような設計されています。」とのことです。要はサーバーサイドのテストということです。
Request Specではコントローラのレスポンスをテストする際、HTTPメソッド（get、post、delete等）が使用できます。

```Ruby:spec/requests/access_to_users_spec.rb 
RSpec.describe "AccessToUsers", type: :request do 

  describe 'ログインしていないユーザーのアクセス制限' do
    it 'ユーザーを削除する' do
      user = User.create( name: 'kojiro',
                          email: 'user@example.com',
                          password: 'password',
                          password_confirmation: 'password')
      expect {
              delete user_path(user)
              }.to change(User, :count).by(0)
    end
  end
end
```
ここでは``delete``メソッドを使用し、ログインしていないユーザーがユーザーを削除しても反映されていないことをテストしています。
``expect {delete user_path(user)}.to change(User, :count).by(0)``は「delete user_path(user)にアクセスした際、アクセス前後のユーザー数の差分が0人ということを予想する」となります。

###Feature Spec
Feature SpecはUIテストになります。
RSpec 3.7から同様の機能を持つ、System Specを使用することを[公式ドキュメント](http://rspec.info/blog/2017/10/rspec-3-7-has-been-released/)で推奨しています。

###System Spec
System SpecはFeature Specと同様、主にUIのテストになります。ブラウザ上で実行するE2E（エンド・ツー・エンド）のテストを記載できます。こちらはフロントのテストですね。また、デフォルトでは、Chromeブラウザーを使用しテストを実行しています。
ブラウザの操作をシミュレートするために**Capybara**を使います。
どのような記載になるか、下記のサンプルコードで確認していきます。

```Ruby:spec/systems/user_logins_spec.rb
RSpec.describe "UsersLogins", type: :system do
  it '有効なログイン' do
    user = User.create( name: 'kojiro',
                        email: 'user@example.com',
                        password: 'password',
                        password_confirmation: 'password')
    visit login_path
    fill_in 'メールアドレス', with: 'user@example.com'
    fill_in 'パスワード', with: 'password'
    click_button 'ログイン'
    expect(current_path).to eq user_path(user)
  end
end
```
一つ一つ内容を確認していきます。

・まず``User.create``でユーザーを作成し保存します。

・``visit``、``fill_in``、``click_button``といったメソッドは、DSLとよばれるCapybaraのドメイン固有言語です。
　1.``visit``はページへの移動を表しています。``visit login_path``でログインページへの移動をします。
　2.``fill_in``はテキストボックスへの文字の入力を表します。``fill_in 'メールアドレス', with: 'user@example.com'``で'メールアドレス'という名前のテキストボックスに'user@example.com'を入力しています。
　3.``click_button``ボタンのクリックを表します。

　その他DSLは[こちら](https://github.com/teamcapybara/capybara#the-dsl)に記載があります。

・``expect(current_path).to eq user_path(user)``では、「現在のパスがuser_path(user)（ユーザー詳細）と同じかどうか」をテストしています。

また、Systemスペックではテストが失敗した際、失敗した内容のエラーメッセージと共に、失敗した箇所のスクリーンショットを表示します。
試しに、エクスペクテーションの``user_path(user)``を``login_path``に変更しテストを実行してみます。
すると下記の出力が出ました。

```
Failures:

  1) UsersLogins 有効なログイン、ログアウト
     Failure/Error: expect(current_path).to eq login_path
     
       expected: "/login"
            got: "/users/1"
     
       (compared using ==)
     
     [Screenshot]: /(プログラムのパス)/myapp/tmp/screenshots/failures_r_spec_example_groups_users_logins_有効なログイン、ログアウト_119.png

     
     # ./spec/system/users_login_spec.rb:14:in `block (2 levels) in <top (required)>'
```

![failures_r_spec_example_groups_users_logins_有効なログイン、ログアウト_119.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687088/e98535ab-4b48-e9c7-48d4-c7cd2afd93b3.png)

このようにスクリーンショットで、結果を期待した``login_path``（ログイン画面）ではなく、``user_path(user)``（ユーザー詳細画面）が表示されていることがわかります。


#まとめ
ちなみにRequest Specでは、Capybaraが使えなかったり、Feature SpecではHTTPメソッドが使えなかったりまします。今実装しようとしているテストが、サーバー側かフロント側か判断が必要になりそうです。


[^1]: スタブとは、コンピュータプログラムのモジュールをテストする際、そのモジュールが呼び出す下位モジュールの代わりに用いる代用品のことです。([wiki](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%96#:~:text=%E3%82%B9%E3%82%BF%E3%83%96%EF%BC%88stub%EF%BC%89%E3%81%A8%E3%81%AF%E3%80%81,%E7%94%A8%E3%81%84%E3%82%8B%E4%BB%A3%E7%94%A8%E5%93%81%E3%81%AE%E3%81%93%E3%81%A8%E3%80%82&text=%E9%80%86%E3%81%AB%E4%B8%8A%E4%BD%8D%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE,%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E5%A0%B4%E5%90%88%EF%BC%89%E3%81%A8%E5%91%BC%E3%81%B6%E3%80%82)より)ここでは、その下位モジュールの代用品を使わずにテスト内でRailsの動作を駆動できるということです。