20
28

More than 5 years have passed since last update.

Rspecの基礎

Last updated at Posted at 2018-12-14

Rspecのことが分からなさすぎたので、調べたことを後で思い出せるように備忘録をつけました。

Rspec

RspecはRuby言語をベースにした「ドメイン特化言語 (Domain Specific Language:DSL)」。ドメイン特化型言語 とは、特定の問題領域 (ドメイン) を記述するために設計された「言語」です。RSpecが特化しているドメインは「開発対象のプログラムの振舞を記述する」という領域。

『TDD』 の進め方と原則

TDD の進め方はいたってシンプルに3つ。

  1. プロダクトコードを書く前にテストコードを書き、それが失敗することを確認する (レッド)
  2. テストに成功するようにプロダクトコードを書く (グリーン)
  3. プログラムの振る舞いを変えないように、プロダクトコードの重複などを整理する (リファクタリング)

TDDの原則もシンプルに3つ。

  1. テストに失敗しない限り、プロダクトコードを書いてはいけない。
  2. プロダクトコードはテストを通るように書く
  3. テストは少しずつ書き進めていく

ソフトウェア

厳密にいえば、ソースコード、すなわちソフトウェア設計は次の2つの要素から構成される。

■テストコード: プログラムがどのように振る舞うべきかを定義したもの
■プロダクトコード: テストコードで定義された振る舞いを実装したもの

RSpecでは、TDDの文脈で開発を駆動する「テスト」をソフトウェア設計の要素であると位置付けている。
そしてRspecの狙いは「プログラマにテストコードが設計であることを明確に意識させることと、プログラマがテストコードをスムースに記述し実行できるようになること」。

そのために、RSpec は振舞定義用の DSL を提供し、様々なテスト関連ライブラリや周辺ツールとの連携を積極的に行い、「統合テスティング環境」となることを目指している。

コントローラーのテスト例

まずは、「assings」を使いたいから、下記を実行

 gem 'rails-controller-testing'

そもそもコントローラーに書く必要があるテストは何?

コントローラーのテストで記述する必要があるのは基本的には下記の3つ。

①受信したリクエストに対して適切なレスポンスを返す
リクエストに対してHTTPレスポンスがステータスコード200を返しているかどうか?

②ビューで使用するのに必要なモデルオブジェクト(インスタンス)をロードする
リクエストされたURLから必要なモデルインスタンスをロードしているかどうか?
適切にインスタンス変数を取り出せているかどうか?

③レスポンスを表示するのに適切なビューを選択する
適切なテンプレートのビューを表示しているかどうか?

indexアクション

※参照URL:https://morizyun.github.io/ruby/rspec-rails-controller.html

describe 'GET #index' do
  let(:articles) {create_list(:article,2)}
  before {get: index, params:{}, session:{}}

 it 'has a 200 status code' do
    expect(assings(:articles)).to have_http_status(:ok)
  end

 it 'assigns @articles' do
    expect(assigns(:articles)).to match_array articles
  end

 it 'render the :index template' do
    expect(response).to render_template :index
  end
end

describe

→describeにはテスト対象を書く。今回の場合は'GET #index'

インスタンス変数の代わりの「let」

上記の例でいうと、

let(:articles) {create_list(:article,2)} 

のように書くと、 {create_list(:article,2)} の中の値を「articles」として参照して取り出すことができる。
ちなみに {create_list(:articles,2)}はarticlesというインスタンスを2回生成するという意味で、第二引数に繰り返す回数を数字で書く。
https://qiita.com/kindai_dai/items/617fd24b978f1ac1135f

before

example の前処理/ 後処理を記述するためのフック。Railsでいうところの、「before_action」のイメージ
上記の例でいうと、①〜③の検証をする前に {get: index, params:{}, session:{}}を実行している。

{①get: index, ②params:{}, ③session:{}} について
※参照:https://qiita.com/kuboon/items/8a7821e06094706d121c

①get :index 第一引数はアクション名であり、実際の url とはまったく関係がない。
②get :show, id: 3 のようにして、 params[:id] に値を直接渡すことができる。
③post :create, session: {user_id: 5}のようにして、 session: {user_id:} に値を直接渡すことができる。
セッションについて https://www.sejuku.net/blog/33677

assignsメソッド

→assignsメソッドはコントローラーのインスタンス変数をテストするメソッド。引数にインスタンス変数をシンボル型で渡す。

matchマッチャ/match_arrayマッチャ

→文字列・ハッシュ・配列の検証に用いるマッチャ。
matchマッチャで配列を検証すると、配列の順番も期待値(expect)と一致している必要がある。
一方、match_arrayマッチャで検証した場合は、要素の個数と各要素の同値性だけを検証し、順番は検証しない。

コントローラのテストで利用できる機能(一例)

■get(action, params={})
指定したアクションへ GET リクエストを送信。

■response()
レスポンスオブジェクトへのアクセサです。レスポンスオブジェクトは HTTP ステータスコードや使用したテンプレートの情報を保持。

■assigns[variable_name]
コントローラのインスタンス変数 @variable_name に代入されたオブジェクトを取得します。キーには “@” を除いたものを文字列またはシンボルで指定。

■post(action, params={})
指定したアクションへ POST リクエストを送信します。パラメータの指定方法は get と同じです。同様に、put や delete もある。

■session()
セッションオブジェクトへのアクセサです。セッション変数の設定やセッション ID を取得する際に利用。

■request()
リクエストオブジェクトへのアクセサです。リクエストの MIME-Type を設定する場合などに利用。

createアクション

この記事が分かりやすい
URL:https://forest-valley17.hatenablog.com/entry/2018/09/29/174446

※show/edit/newは下記URLを参照。
URL:https://morizyun.github.io/ruby/rspec-rails-controller.html

createアクションで、if @変数.save のように条件分岐をしている場合は、
下記のようにcontextで条件別にグループ化する。

describe 'Post #create' do
  context "@articleが保存できた時" do

    it "データベースに値が保存される" do
    end

    it "正しいビューに変遷する" do
    end

  end

  context "@articleが保存できなかった時" do

    it "データベースに値が保存されない" do
    end

    it "正しいビューに変遷する" do
    end

  end

 end

以下、記述例

describe "#create" do
  context "as an authenticated user" do
    before do
      @user = FactoryBot.create(:user)
    end

    it "adds a project" do
      project_params = FactoryBot.attributes_for(:project)
      sign_in @user
      expect do
        post :create, params: {project: project_params}
      end.to change(@user.projects, :count).by(1)
    end
  end

  context "as a guest" do
    it "returns a 302 response" do
      project_params = FactoryBot.attributes_for(:project)
      post :create, params: {project: project_params}
      expect(response).to have_http_status "302"
    end

    it "redirects to the sign-in page" do
      project_params = FactoryBot.attributes_for(:project)
      post :create, params: {project: project_params}
      expect(response).to redirect_to "/users/sign_in"
    end
  end
end

sign_inを使用して、ログイン状態をシミュレートしてテストをする場合は
spec/rails_helper.rbに下記記述を追加する必要がある。

config.include Devise::Test::ControllerHelpers, type: :controller

change + from / to / by

例文

# popメソッドを呼ぶと配列の要素が減少することをテストする
x = [1, 2, 3]
expect(x.size).to eq 3
x.pop
expect(x.size).to eq 2

changeで置き換えると、、、

x = [1, 2, 3]
expect { x.pop }.to change{ x.size }.from(3).to(2)

読み方は下記の通り

expect{ X }.to change{ Y }.from(A).to(B) = 「X すると Y が A から B に変わることを期待する」

from().to()は「by」で書き換えることもできる、、、

x = [1, 2, 3]
expect { x.pop }.to change { x.size }.by(-1)
x = [1, 2, 3]
expect { x.push(10) }.to change { x.size }.by(1)

配列要素の個数はsizeじゃなくて、countも使える

class User < ActiveRecord::Base
  # dependent: :destroy を付けたので、userを削除するとblogも削除される
  has_many :blogs, dependent: :destroy
end

class Blog < ActiveRecord::Base
  belongs_to :user
end
it 'userを削除すると、userが書いたblogも削除されること' do
  user = User.create(name: 'Tom', email: 'tom@example.com')
  # user が blog を書いたことにする
  user.blogs.create(title: 'Rspec必勝法', content: 'あとで書く')

 expect {user.destroy}.to change { Blog.count}.by(-1)
end

このように、change マッチャを使うと、「Xに対するある操作が、一見無関係なYに影響を与える」といった検証内容を簡潔に表現することができる。

モック(Mock)

何らかの理由で本物のプログラムが使えない、もしくは使わない方がよいケースでモックが使われる。
使い道としては、外部のAPIを利用しなければならない場合など。
例えば、TwitterのAPI(gem)経由でツイートするプログラムをテストする場合は、テストコードの内容がそのまま
全世界にツイートされてしまう。それは嫌だ。
ということで、モックを使えば、Twitterと同じ機能を持ち且つテストコードでのみ起動するプログラムを作ることができる。

以下、簡単な例

Ruby.controller.rb
# 注:本当に動かす場合はtwitter gemが必要です
require 'twitter'

class WeatherBot
  def tweet_forecast
    twitter_client.update '今日は晴れです'
  end

  def twitter_client
    Twitter::REST::Client.new
  end
end
Ruby.Mock.rb
it 'エラーなく予報をツイートすること' do
  # Twitter clientのモックを作る(doubleで作れる。)
  twitter_client_mock = double('Twitter client')
  # updateメソッドが呼びだせるようにする
  allow(twitter_client_mock).to receive(:update)

 # WeatherBotクラスのオブジェクトを作成
  weather_bot = WeatherBot.new
  # twitter_clientメソッドが呼ばれたら上で作ったモックを返すように実装を書き換える
  allow(weather_bot).to receive(:twitter_client).and_return(twitter_client_mock)

  expect{ weather_bot.tweet_forecast }.not_to raise_error
end

そのほか、詳細は下記参照

※参考ページ※ これらを読んだら基本的なRspecのことは分かります。
https://morizyun.github.io/ruby/rspec-rails-controller.html
https://qiita.com/jnchito/items/42193d066bd61c740612
https://qiita.com/jnchito/items/640f17e124ab263a54dd
https://qiita.com/jnchito/items/a4a51852c2c678b57868
https://magazine.rubyist.net/articles/0021/0021-Rspec.html
https://qiita.com/namitop/items/bf455f8383181ff6edf3
https://magazine.rubyist.net/articles/0023/0023-Rspec.html#%E6%B0%B4%E5%B9%B3%E5%88%86%E5%89%B2%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81%E3%81%A8%E5%9E%82%E7%9B%B4%E5%88%86%E5%89%B2%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81
https://forest-valley17.hatenablog.com/entry/2018/09/29/174446
https://qiita.com/morrr/items/f1d3ac46b029ccddd017
https://qiita.com/muran001/items/436fd07eba1db18ed622

20
28
2

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
20
28