読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

RSpec/Capybara/Capybara-Webkitの導入方法

Rails中級 まとめ Rails Test

RSpecの基本について理解している人を対象に、「RSpecのテストに必要なGem、モデル、コントローラー、Feature(Capybare)、JavaScriptなどの基本的なテストの書き方」についてまとめました。

下記のサイトも参考になります。

動作環境

  • Mac OS X 10
  • Ruby 2.1
  • Rails 4.1
  • rspec-rails 3.1.0
  • shoulda-matchers 2.6.2
  • factory_girl_rails 4.4.1
  • capybara 2.4.1
  • Phantomjs 1.9.8
  • poltergeist 1.5.1
  • capybara-webkit 1.3.0
  • database_cleaner 1.3.0

目次

  1. UTからE2Eテストのための準備(RSpec, FactoryGirlなど)
  2. Modelのテスト
  3. Controllerのテスト
  4. Helperのテスト
  5. APIのテスト
  6. E2E(Features)のテスト
  7. E2Eテスト(JavaScript)のための準備(Capybara-Webkitなど)
  8. E2Eテスト(JavaScript)のための準備(PhantomJS, Poltergistなど)

1. UTからE2Eテストのための準備(RSpec, FactoryGirlなど)

Gemfileに以下を追加します。

# Gemfile

group :development, :test do
  # デバッグに便利なGem
  gem 'pry-rails'  # rails console(もしくは、rails c)でirbの代わりにpryを使われる
  gem 'pry-doc'    # methodを表示
  gem 'pry-byebug' # デバッグを実施(Ruby 2.0以降で動作する)
  gem 'pry-stack_explorer' # スタックをたどれる

  # RSpecの基本的なGem
  gem "rspec-rails"
  gem "shoulda-matchers"
  gem "factory_girl_rails"

  # E2Eテストを実施するために必要
  gem "capybara"
end

rspecの設定ファイルを作成する

bundle install
rails g rspec:install

設定ファイルに設定をする(Addを記載している)

# spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'capybara/rails'   # Add
require 'capybara/rspec'   # Add


RSpec.configure do |config|
  # FactoryGirl でレシーバ無しでFactoryGirlのメソッド呼び出しを可能にする
  # Ex: FactoryGirl.create(:post) => create(:post)
  config.include FactoryGirl::Syntax::Methods  # Add
  ...
end


2. Modelのテスト

基本
  • ビジネスロジックが集まる箇所なので、基本的にはテストを書く。
  • attr_accessorpresence: trueなどバグが入る可能性が少ない箇所はテストは書かない。
ModelとFactoryの生成
$ rails g rspec:model post
      create  spec/models/post_spec.rb
      invoke  factory_girl
      create    spec/factories/posts.rb
モデルのテストの作成
# テスト対象のモデルファイル
# app/models/post.rb
class Post < ActiveRecord::Base
  validates :title,   presence: true, uniquness: true
  validates :content, presence: true, length: { maximum: 500 }
end


# テストファイル
# spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, :type => :model do
  describe "#title" do
    it { is_expected.to validate_presence_of(:title) }
    it { is_expected.to validate_uniqueness_of(:title) }
  end

  describe "#content" do
    it { is_expected.to validate_presence_of(:content) }
    it { is_expected.to ensure_length_of(:content) }
  end
end

参照:Shoulda Matcherの公式ドキュメント v2.7.0



3. Controllerのテスト

基本
  • E2E(Features)と重複するので、画面入力で制御されていてその異常パラメーターがこないなどの異常系をテストする
コントローラーのテストで検証すること

- レスポンスのステータス

expect(response).to be_success
expect(response['Contetn-Type']).to =~ %r^text/html!
  • ビューに渡されるインスタンス変数
expect(assins(:entries)).to == Entry.all
  • 描画したテンプレート
expect(response).to render_template('index')
expect(response).to redirect_to(entries_url)
ModelとFactoryの生成
rails g rspec:controller post
      create  spec/controllers/post_controller_spec.rb
コントローラーのテスト作成
# テスト対象のコントローラーファイル
# app/controllers/posts_controller.rb

# POST /posts
# POST /posts.json
def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.html { redirect_to @post, notice: 'Post was successfully created.' }
      format.json { render :show, status: :created, location: @post }
    else
      format.html { render :new }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end


# テストファイル
# spec/controllers/posts_controller_spec.rb
require 'rails_helper'

RSpec.describe PostsController, :type => :controller do

  def valid_attributes
    { title: "title", content: "content" }
  end

  def invalid_attributes
    { title: "", content: "" }
  end

  describe "POST create" do
    describe "with valid params" do
      it "creates a new Post" do
        expect {
          post :create, {:post => valid_attributes}
        }.to change(Post, :count).by(1)
      end

      it "assigns a newly created post as @post" do
        post :create, {:post => valid_attributes}
        expect(assigns(:post)).to be_a(Post)
        expect(assigns(:post)).to be_persisted
      end

      it "redirects to the created post" do
        post :create, {:post => valid_attributes}
        expect(response).to redirect_to(Post.last)
      end
    end

    describe "with invalid params" do
      it "assigns a newly created but unsaved post as @post" do
        post :create, {:post => invalid_attributes}
        expect(assigns(:post)).to be_a_new(Post)
      end

      it "re-renders the 'new' template" do
        post :create, {:post => invalid_attributes}
        expect(response).to render_template("new")
      end
    end
  end
end

参照:Shoulda Matcherの公式ドキュメント v2.7.0




4. Heloperのテスト

基本
  • ヘルパーはロジックをがっつり記載する箇所なので基本的にテストをする
helper_specの生成
rails g rspec:helper application
      create  spec/helpers/application_helper_spec.rb
ヘルパーのテストの作成
# テスト対象のヘルパーファイル
# app/helpers/application_helper.rb
module ApplicationHelper
  def active_if_current(path)
    'active' if current_page?(path)
  end
end


# テストファイル
# spec/helpers/application_helper_spec.rb
require 'rails_helper'

RSpec.describe ApplicationHelper, :type => :helper do
  describe "active_if_current" do
    subject { helper.active_if_current("/any_path") }

    context "現在のページが引数のパスと等しい場合" do
      it do
        allow(helper).to receive(:current_page?).and_return(true)
        expect(subject).to eq "active"
      end
    end

    context "現在のページが引数のパスと等しくない場合" do
      it do
        allow(helper).to receive(:current_page?).and_return(false)
        expect(subject).to be_nil
      end
    end
  end
end


5. APIのテスト

基本

  • APIのテストには、requestsを使います。

詳細は次の記事を参照してください。

RailsでAPI作成とAPIのテストのまとめ - Rails Webook




6. E2E(Features)のテスト

基本
  • E2E(Features)でルート、コントローラー、ビュー、モデルのつながりの1機能のテストをする
Feature Specの生成
rails g rspec:feature post
      create  spec/features/posts_spec.rb
Feature Specのテストの作成

テスト対象のファイル(ルート、コントローラー、ビュー、モデルファイル)

  • config/routes.rb
  • app/models/post.rb
  • app/controllers/posts_controller.rb (new, createメソッド)
  • app/views/posts/new.html.erb
  • app/views/posts/show.html.erb
  • ...


テストファイルのサンプルです。

# spec/features/post_spec.rb
require 'rails_helper'

RSpec.describe "Posts", :type => :feature do
  describe "新規登録" do
    before do
      visit posts_path      # 一覧画面に遷移
      click_link "New Post" # "New Post"リンクを押す
    end

    it "投稿新規作成画面が表示されること" do
      expect(page.current_path).to eq new_post_path
    end

    it "投稿を新規作成できること" do
      # テキストフィールドに値を入力する
      fill_in "Title",   with: "タイトル"
      fill_in "Content", with: "本文"
      click_button "Create Post"

      # 画面に"Post was ..."と表示されていることを確認
      expect(page).to have_content "Post was successfully created."
      expect(page).to have_content "タイトル"
      expect(page).to have_content "本文"

      # データベースに登録された内容を確認(必要に応じて確認)
      post = Post.last
      expect(post.title).to eq "タイトル"
      expect(post.content).to eq "本文"
    end
  end
end

CapybaraのDSL:CapybaraのDSL




7. E2Eテスト(JavaScript)のための準備(Capybara-Webkitなど)

QTツールのインストール方法を見て、QTツールをインストールする。

GemfileにJSをテストするためのドライバーのCapybara-Webkitを追加する。
ドライバは別プロセスで実行されるので、テスト毎にデータベースを元の状態に戻せないので、元に戻せるようにするためにDatabase Cleanerも追加する。
Linuxの場合は、headlessというgemと、xvfb-runというユーティリティのインストールが必要かもしれない。
参照: capybara-webkit on CI

# Gemfile
group :test do
  ...

  # E2Eテスト(JavaScript)を実施するために必要
  gem "capybara-webkit"
  gem "database_cleaner"


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

bundle install


次に、DatabaseCleanerを利用するように設定を行います。

# spec/rails_helper.rb

...

require 'capybara/rails'
require 'capybara/rspec'

# 追加箇所 JavascriptのDriverをwebkitにする
Capybara.javascript_driver = :webkit

...

RSpec.configure do |config|

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  # config.use_transactional_fixtures = true
  # 追加箇所 trueからfalseにする
  config.use_transactional_fixtures = false

  # 追加箇所 開始
  # suite: RSpecコマンドでテストを実行する単位
  # all:  各テストファイル(xxx_spec.rb)単位
  # each: 各テストケース(it)単位
  config.before(:suite) do
    DatabaseCleaner.clean_with :truncation  # テスト開始時にDBをクリーンにする
  end

  # js以外のテスト時は通常のtransactionでデータを削除する
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  # jsのテスト時はtruncationで削除する
  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  config.after(:all) do
    DatabaseCleaner.clean_with :truncation # all時にDBをクリーンにする
  end
  # 追加箇所 終了

  ...
end


これで、Specファイル内に次ようにjs: trueと追記すればJavaScriptのテストが可能になります。
js: trueを渡したブロック内がJavaScript用のドライバを利用してテストされます。

describe "JavaScriptのテスト", js: true do
  # JavaScriptがつかわれている箇所のテスト
end

...

it "JavaScriptのテスト", js: true do
  # JavaScriptがつかわれている箇所のテスト
end


8. E2Eテスト(JavaScript)のための準備(PhantomJS, Poltergistなど)

  • E2E(JavaScript)でE2Eと一緒に、AjaxなどのJavaScriptのテストを行う
  • ドライバーはseleniumなどあるが実行時間がかかり、画面が必要なのでCIなどで実行しにくいので、capybara-webkitかpoltergeistが採用されている

PhantomJSをインストールする。

GemfileにJSをテストするためのドライバーのPoltergeistを追加する。
ドライバは別プロセスで実行されるので、テスト毎にデータベースを元の状態に戻せないので、元に戻せるようにするためにDatabase Cleanerも追加する。

# Gemfile
group :test do
  ...

  # E2Eテスト(JavaScript)を実施するために必要
  gem "poltergeist"
  gem "database_cleaner"


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

bundle install


次に、DatabaseCleanerを利用するように設定を行います。

# spec/rails_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'capybara/rails'
require 'capybara/rspec'
require 'capybara/poltergeist'  # Add

Capybara.javascript_driver = :poltergeist  # Add

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  # FactoryGirl でレシーバ無しでFactoryGirlのメソッド呼び出しを可能にする
  # Ex: FactoryGirl.create(:post) => create(:post)
  config.include FactoryGirl::Syntax::Methods

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  # config.use_transactional_fixtures = true
  config.use_transactional_fixtures = false  # Change

  # Add Begin
  # suite: RSpecコマンドでテストを実行する単位
  # all:  各テストファイル(xxx_spec.rb)単位
  # each: 各テストケース(it)単位
  config.before(:suite) do
    DatabaseCleaner.clean_with :truncation  # テスト開始時にDBをクリーンにする
  end

  # js以外のテスト時は通常のtransactionでデータを削除する
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  # jsのテスト時はtruncationで削除する
  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  config.after(:all) do
    DatabaseCleaner.clean_with :truncation # all時にDBをクリーンにする
  end
  # Add End

  ...
end