来年に向けて「RSpecのE2Eテスト方法」まとめてみたよ_φ(・・*)

  • 70
    Like
  • 0
    Comment
More than 1 year has passed since last update.

RSepcをつかったModelとControllerの基本的な(ユニット)テストは、
なんとか書けるようになってきましたが、

 「ちゃんとテストやれてる!」

と言えるにはまだまだ、

  • インテグレーションテスト
  • エンドツーエンドテスト
  • 受け入れテスト

色々とやれることはあります。

直近、ユーザーのアクションがより重要なサービスの開発を進めることもあり、
「エンドツーエンドテスト」をやれる良さ気な方法は何か調べてみて、動くまでをまとめてみした。
※結論からいうと、「RSpec ☓ Capybara ☓ Poltergeist」で書くことにした記事です。

目次

過去記事

※RSpecの最初のセットアップやModel Controllerユニットテストの
書き方例は下記にまとめてみましたのでよかったら。

E2Eテストに何が必要?

シナリオ/テストコード

  • シナリオが書ける
  • シナリオを実行して確認するテストコードが書ける

 →Cucumber, Turnip, RSpec feature spec

画面操作

  • フォームやボタンを操作できる
  • 表示されている画面要素が正しいか確認できる
  • 画面遷移が確認できる

 → Capybara

js動作

  • js動作環境でテストコードが実行できる

 → selenium, Capybara-webkit, Poltergeist 等

  • js動作環境でのテストをheadlessで高速にできるようにする

 → Capybara-webkit, Poltergeist 等

デバッグ

  • デバッグのためのキャプチャ保存

 → launchy(Capybaraの依存関係に含まれる), poltergeistのスクリーンショット保存

etc..

シナリオ(ステップ)を書くのはどれがいい?

ユーザーのシナリオごとにテストコードを書いていくには、色んなライブラリもあったりするようだったので、ざっくり調べてみました。

Cucumber

https://cucumber.io/

日本語でシナリをを書いて行ける。それを元に実際のテストプログラムfileは別で作成。
変化が激しく、学習コストが高い様子。

Turnip

https://github.com/jnicklas/turnip

Cucumberの改善としてだされたらしいので、日本語シナリオ使いたいと思ったら、こちらのほうが今は良さそうです。
こちらも日本語でシナリオを書くファイルと、ステップごとに実際のテスコードを書くファイルと分けて書くようです。

※参考
Rubyist Magazine - エンドツーエンドテストの自動化は Cucumber から Turnip へ

RSpecの feature specのみ

https://www.relishapp.com/rspec/rspec-rails/docs/feature-specs/feature-spec

上記のような日本語のシナリオだけをまとめることはせず、
単純にシナリオごとにテストコードを書いていくようです。

結論

今関わっているプロジェクトでは、非エンジニア以外にテストシナリオを
説明することはあまりないので、普通にRSpecのfeature specで書いていこうとおもいます^^;

js動作のドライブはどれがいいの?

selenium vs Capybara-webkit vs Poltergeist

jsが動く環境での確認をしたいだけなら、seleniumだけで良いようですが、
調べていくと、Poltergeistが良さそうでした。

  • seleniumは、
     →ブラウザを必要とするため、テストに時間がかかる

  • Capybara-webkitは、
     →Qt, xvfb が必要
      →準備にちょっと色々めんどうそうなのがある

  • Poltergeistは、
     →phantomjs を使う
      →phantomjs入れて gemに書くだけで簡単そう

参考
poltergeist - Capybaraを使う際に知っておきたいこと - Qiita

E2Eの環境セットアップ方法は?

上記をふまえて、
「RSpec ☓ Capybara ☓ Poltergeist」が使えるまでを設定してみました。

Capybara セットアップ

rollback の仕方が変わるそうなので、database_cleanerの設定が必須となるようです。

Gemfile

jnicklas/capybara

group :test do
  gem 'database_cleaner', '~> 1.3.0' # テスト実行後にDBをクリア
  gem 'capybara', '~> 2.4.3'         # ブラウザでの操作をシミュレートしてテストができる

rails_helper.rb

spec/rails_helper.rb

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

RSpec.configure do |config|
  config.use_transactional_fixtures = false #trueでいける方法もあるようです

  # 省略

  # テスト実行後にDBをクリア
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation,{:except => %w{categories jobs initials job_positions}})
    DatabaseCleaner.strategy = :transaction
  end

  config.before :each do
    DatabaseCleaner[:mongoid].start
    DatabaseCleaner.start
  end

  config.after :each do
    DatabaseCleaner[:mongoid].clean
    DatabaseCleaner.clean
  end

自分は、mongoとmysqlを使っているので上記のように書きました。
他DatabaseCleanerについて詳しくは、下記を
DatabaseCleaner/database_cleaner

deviseでのログイン対応

ログイン機能の実装には、お馴染みの devise を使っています。
ログインユーザーを想定して、Capybaraをつかった操作をするには、追加で設定が必要です。

設定追加

spec/rails_helper.rb

RSpec.configure do |config|
  config.include Warden::Test::Helpers
  # 省略
  config.before(:suite) do
     Warden.test_mode!
     # 省略
  end

  config.after :each do
    Warden.test_reset!
    # 省略
  end

spec コードの中で、下記のように書くとログインしたことになる

spec/features/user_actions_spec.rb
feature "UserActions", :type => :feature do

  context "ログインしている場合" do
    before(:each){
      # devise login
      user = create(:user)
      login_as(user, :scope => :user)
    }

※参考
How To: Test with Capybara · plataformatec/devise Wiki

Poltergeist セットアップ

phantomjs をインストールします
自分の場合は、vagrant CentOSに入れたかったので下記を実行しました

$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2
$ tar xvjf phantomjs-1.9.8-linux-x86_64.tar.bz2
$ cd phantomjs-1.9.8-linux-x86_64
$ sudo ln -sf "$(pwd)"/bin/phantomjs /usr/local/bin/phantomjs

Gemfile

teampoltergeist/poltergeist

  gem "poltergeist" # headlessでjsテストをする(phantomjsが必要)

rails_helper.rb

spec/rails_helper.rb
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist

あとは、feature specのscenario のoptionにjs: trueをつければjsが動作する&headlessな環境でテスト出来ます。

※参考

feature specの書き方は?

サンプル

spec/features/user_actions_spec.rb
require 'rails_helper'

feature "UserActions", :type => :feature do

  context "ログインしている場合" do
    before(:each){
      # devise login
      user = create(:user)
      login_as(user, :scope => :user)
    }

    scenario "#btn1ボタンを押すと「詳細ページ」から「作成ページ」へ遷移できる" do
      # 詳細ページへ
      item = create(:item)
      visit item_path(item.seq)

      # ボタンクリック
      find('#btn1').click

      expect(current_path).to eq '/items/new'
    end
  end


  context "ログインしていない場合" do
    scenario "#btn1ボタンを押すとログインモーダルが立ち上がる", js: true do
      # 詳細ページへ
      item = create(:item)
      visit item_path(item.seq)

      # ボタンクリック
      find('#btn1').click

      expect(page).to have_css "div.remodal-wrapper.remodal-is-opened"
    end

    scenario "jsが有効でない場合、#btn1ボタンを押してもremodalが立ち上がらない" do
      # 詳細ページへ
      item = create(:item)
      visit item_path(item.seq)

      # ボタンクリック
      find('#btn1').click

      expect(page).not_to have_css "div.remodal-wrapper.remodal-is-opened"
    end
  end
end

実行

$ rspec spec/features/user_actions_spec.rb

簡単な補足/注意点

  • jsが動作する環境でのテストは、js: trueとします
  • pageでCapybaraをつかっていろんな情報が取得できるので binding.pryをして  ls pageとすると色々便利なメソッドが用意されていることが分かるとおもいます
  • Capybaraで、対象のセレクタを指定するときに、同名のClassがあるものを指定するとエラーになります。

Cabybara書き方参考

Cabybaraでは、こんな時どう書くのか、まとめられた記事がいくつかあって助かりましたm(_ _)m

デバッグする

Launchyのおかげで、下記の一行を、確認したい位置(specコード内)に追記すると、
その時点のhtmlをtmp/capybaraに保存してくれます。

spec/features/user_actions_spec.rb
 save_and_open_page

poltergeistのスクリーンショットを保存する機能を使うこともできるようです

spec/features/user_actions_spec.rb
page.save_screenshot('/path/to/file.png')

https://github.com/teampoltergeist/poltergeist#taking-screenshots-with-some-extensions

参考
poltergeist - Capybaraを使う際に知っておきたいこと - Qiita

おわりに

まだ自分は、エンジニアに本業を絞って2年ですが、
いいシステムを作る&安定した運用には、テストは重要ではないかなと思ってます。

。。そう思いつつ、普段、忙しさに追われたり、テストを書くとはどういうことなのか、
模索しているところではありますが、^^;

目の前に囚われ過ぎない、後のことも考えたシステム作りの文化に貢献できる
エンジニアになってけるようさらに精進してきたいと思います。

2016年も頑張るぞ(*´ω`)ノ