LoginSignup
14
10

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-04

はじめに

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

RSpecの主なディレクトリ

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

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

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

主なSpecの役割について

ここでは、Model SpecRequest SpecFeature SpecSystem Specの役割について紹介します

Model Spec

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

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ではユーザーを作成しています。

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

expectではエクスペクテーション(expectation、期待する内容)を記載しています。
 expect(user).to_not be_validは「userが有効ではないか?」ということをテストしています。
 英文のようにテストを記載できるのがRSpecの利点の一つですね。
 to_not be_validの部分はマッチャ(matcher)と呼ばれ様々な種類があります。
 その他のマッチャについてはこちらに記載があります。

Request Spec

Request Specは公式ドキュメントよると、「ルーティングを含むフルスタックを介して、スタブ1なしで動作を駆動するような設計されています。」とのことです。要はサーバーサイドのテストということです。
Request Specではコントローラのレスポンスをテストする際、HTTPメソッド(get、post、delete等)が使用できます。

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を使用することを公式ドキュメントで推奨しています。

System Spec

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

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でユーザーを作成し保存します。

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

 その他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

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

まとめ

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


  1. スタブとは、コンピュータプログラムのモジュールをテストする際、そのモジュールが呼び出す下位モジュールの代わりに用いる代用品のことです。(wikiより)ここでは、その下位モジュールの代用品を使わずにテスト内でRailsの動作を駆動できるということです。 

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10