RailsのRSpecで行うべきテストの種類と注意点

[株式会社TECH LUCK](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)という会社で代表兼エンジニアをしている齊藤です。

DXプロジェクト、開発プロジェクト、Rails開発などでお困りごとがありましたら[弊社HP](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)からご相談をいただけますと幸いです。
以下のような問題に対応することが可能です。
- プロジェクトでRailsエンジニアが足りなくて困っている
- Railsのバージョンアップをしたいがノウハウ・リソースが足りなくて困っている
- オフショア開発をしているが、要件の齟齬やコード品質が悪いので改善したい

また、Railsエンジニアも募集しておりますので、興味がありましたら[弊社HP](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)からご連絡いただけますと幸いです。

# 前提

Railsプロジェクトの中でRSpecでテストコードを記述する際に、Feature Spec, Sysmtem Spec, Controller Specなど多くのテストの種類があり、混乱する方も多いと思います。

この記事では、大枠としてどのようなテストコードを書いていくべきか、RSpecの諸々のSpecの違いは何かをまとめました。

**注意点**
この記事ではテストコードの書き方については触れませんのでご了承ください。

# テストの種類
システム開発におけるテストの種類は、大きく以下の4つに分かれます。（1回1回のテストの範囲が小さい単位順に記載しています。）

| テストの種類 | 別名 | テスト内容 |
| --- | --- | --- |
| 単体テスト | ユニットテスト/UT | 1つのクラス、関数、メソッドが正しく動作するかどうかを確認します。 |
| 結合テスト | 連結テスト、統合テスト、インテグレーションテスト/IT | 特定の機能が正しく動作するかどうかを確認します。投稿作成画面や投稿編集画面などの画面単位でのテストが機能テストとなります。 |
| システムテスト | システムテスト/ST、エンドツーエンドテスト / E2E | ユーザーが実際に使用する一連の流れを再現しシステム正しく動作するかどうかを確認します。特定のクラス、関数、メソッドなどのコンポーネントや、投稿作成の機能という単位ではなく、システム全体のテストを指しています。機能テストとは違い、「ログインして、投稿を作成して、投稿を編集する。」という一連の流れをテストします。 |

# RSpecのSpecの種類
Specとは、RSpecの以下のコードの中の `type: :controller`の部分のことです。

```ruby:spec/controllers/products_controller_spec.rb
require 'rails_helper'

describe ProductsController, type: :controller do
 # ~~~省略~~~
end
```

この`type`の箇所の引数を変更することで、コントローラー、モデルなどの機能ごとに使えるテストコードの中で使えるメソッドが変化します。
例えば、コントローラーで必要なテストメソッド（Matcher/マッチャーともいいます）と、モデルで必要なテストメソッドは違うものになるためです。

コントローラーのテストを実行する際に、 **ProductsControllerのindexメソッドにリクエストが来た場合には、index.html.erbが表示される** というテストをします。

`type: :controller`でそのためのコードは以下のようなテストコードを書く形になります。

```ruby:products_controller_spec.rb
expect(response).to render_template :index
```

上記のコードで`type: :model`を指定した場合には上記のコード自体が実行されずに落ちてしまう形になります。


RSpecには以下の種類のSpecがあります。
※全部を調べられてはいないので、他にあれば教えてください。

| RSpecで作成するディレクトリ | Specの種類 | どのテストで使うか |
| --- | --- | --- |
| spec/view | :views | ビューの単体テスト |
| spec/models | :model | モデルの単体テスト |
| spec/controllers | :controller | コントローラーの単体テスト |
| spec/helpers | :helper | ヘルパーメソッドの単体テスト | 
| spec/routing | :routing | ルーティングの単体テスト |
| spec/jobs | :job | ジョブの単体テスト |
| spec/mailer | :mailer | メーラーの単体テスト |
| spec/request, spec/integration, spec/api | :request | 結合テスト |
| spec/feature | :feature| システムテスト |
| spec/system | :system | システムテスト |

**※Feature SpecとSystem Specの違いについて**

Feature SpecとSystem SpecはどちらもRSpecで提供されているE2Eテストの機能です。
Feature Specのほうが歴史が長く、古いバージョンのRailsプロジェクトではよく使われています。しかし、Rails5.1で System SpecがRSpecに導入されており、現状では両方とも使うことができますが、公式のREADMEではSystem Specを推奨しているようです。Feature Specは引き続きサポートしていくとのことです。

# RSpecでの書き方について
## 単体テスト
特定のモデルの1つのメソッド、関数ごとにテストをする形になります。

以下はUserモデルの1つのバリデーションのテストになります。

```ruby:spec
require 'rails_helper'

RSpec.describe User, type: :model do
  describe '#create' do
    before do
      @user = FactoryBot.build(:user)
    end

    it "nicknameが空だと登録できない" do
      @user.nickname = nil
      @user.valid?
      expect(@user.errors.full_messages).to include("Nickname can't be blank")
    end
  end
end
```

このような形で1つ1つのテストをする形になります。

## 結合テスト
`spec/request`、`spec/integration`、`spec/api`のtypeを利用して、テストを行なっていきます。
どのようなテストをメインでテストをするのかというと、APIなどで ***どのURLにどのようなパラメーターを投げたら、どのよなうなレスポンスを返すのか*** というテストをします。
APIなどの返却は結合テスト、通常のどの画面が表示されるのかなども含めたテストはシステムテストで書いてしまう方がいいとも言われています。[こちらの記事が参考](https://qiita.com/tofuonfire/items/cc45eb35704d898ee592)になります。

サンプルとする以下のコードは[こちらの記事](https://qiita.com/ryouzi/items/4d198eac8df5958ddb1e)から引用したコードになります。

```ruby:spec/requests/sign_up_spec.rb
RSpec.describe "Users signup", type: :request do
  example "valid signup information" do
    get signup_path
    expect {
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    }.to change(User, :count).by(1)
    redirect_to @user
    follow_redirect!
    # Test
    assert_template 'users/show'
    assert is_logged_in?
  end
end
```



## システムテスト
システムテストはユーザーが実際に利用する画面を操作を再現してのテストになります。
**この画面に訪れて、このボタンをクリックしたら、ツイート作成画面に遷移して、必須項目を入力して作成ボタンを押すと、DBに記事が1つ作成され、投稿一覧画面が表示される** というようなテストを記載します。

また、画面のテストを行うフレームワークである`Capybara`というライブラリの導入も必要になります。

```ruby:spec/features/tweet_spec.rb
describe 'ツイート作成', type: :system do
  describe '通常のユーザーの場合' do
    let!(:article) { create(:user) }

    it 'ツイートを作成することができる' do
      visit tweets_path
      fill_in 'tweet[content]', with: '今日はいいことがあった！'
      click_on 'ツイート新規作成'
      expect(page).to have_current_path(tweets_path)
    end
  end
end
```


# 参考記事
## テストについて
https://qiita.com/ktarow/items/8c3d94d6c21a0c86b799

## 機能テストについて
https://nihonbuson.hatenadiary.jp/entry/2019/12/03/003000

## RSpecについて
https://mgmmy.hatenablog.com/entry/2019/12/16/002020

https://qiita.com/niwa1903/items/e1ad9ab39811aa1cf312

https://qiita.com/shima-zu/items/3eb08662f2da05196ec9

## RSpecでの単体テストについて
https://qiita.com/keiya_man/items/27200393889ca674f6cb

## RSpecのSpec typeについて
https://blog.ingage.jp/entry/2021/04/26/090000

## System SpecとFeature Specの違いについて
https://qiita.com/shima-zu/items/3eb08662f2da05196ec9

## システムスペックとリクエストスペックの使い分け
https://qiita.com/tofuonfire/items/cc45eb35704d898ee592