Ruby on Rails

Ruby on RailsでE2Eテストを実装した

はじめに

最近Railsを触り始めました。だんだんページ数も増えてくるのに加えてサイト構造の変更がわりと頻繁に起こったりするのでその都度の確認がとても大変になるのに対して、毎回全てを網羅しているわけではないので気付かない部分で500エラーをはいてたりすることもしばしばありました。全てはテストの実装をすっとばして開発していたのが原因です。不安な気持ちがありながら毎回デプロイしていました。テストをしっかり作っていればその点の確認作業が自動で出来るのに加えて全てを網羅するようにかけれていれば気付かないところでのエラーも防ぐことが出来ます。

今回テストを実装したのでそれにあたって試行錯誤したことをまとめていこうと思います。

テストの種類

テストの種類は大きく分けて

  • ユニットテスト ・・・ プログラム全体ではなくモジュールごとに個別に行うテスト
  • E2E(End-to-End)テスト ・・・ 実際にブラウザを操作して意図したとおりに表示、操作など出来るかをテストする

があります。

ユニットテストは細かい単位で例えばメソッドごとなどでテストを行います。データは実際のものを使うと網羅性が少ないので、想定されるパターンにしっかり対応できるかチェックできるようにテスト用にデータを定義するのが一般的です。
対してE2Eは実際のページ単位という大きな単位でテストします。なのでテスト用にデータを定義したとしても実運用したときのデータでエラーになることもあるので、実際のデータでテストしたほうがいいのではないかと思います。

理想は両方行うのがいいですが、今回はそこまでかけれる時間もなかったので少ない工数で手っ取り早く作る必要がありました。

今回の状況として、500エラーや404エラーが気付かないところでちょくちょく起こっていて、それをチェックする手段が手動で行うしかなかったので、ページが正常に表示されるかをチェックするのを導入するのをまずやらなければいけないと思いました。

なので今回はテスト用のデータを定義する必要もなく、全ページを自動でチェックできるように、実際のデータを使ってのE2Eテストを実装することにしました。

今回実装したいもの

今回は最初だったので最低限の項目をチェックできるようなものを実装しようと思いました。

  • 各ページにアクセスしてステータス200が返ってくるか
  • 404であるべきなURLはちゃんと404になっているか
  • リダイレクトするURLはちゃんとリダイレクト出来ているか
  • 個別に指定してるいくつかmetaタグがちゃんと正常に入っているかを
  • ページ内のリンクのURLにアクセスして200が返ってくるか

理想

  • プルリク前に実行してエラーページがないかどうかチェックできるようにする
  • 本番環境でも定期的に実行して、エラーが有った場合にSlackなどに通知が行くようにできる。

仕様ツール

GoogleAppScript

E2Eテストで何を使用するかを検討していたときにまず最初に浮かんだのがGoogleAppScriptです。
スプレッドシートでリストを作成しておけばステータスチェックぐらいであればちょっとの実装だけで済みそうだし、定期実行の設定も簡単にできるので便利ではないかなと思ったからです。

しかし、開発環境でチェックするのが面倒そうだったのとURLの管理が面倒だと思ったのでやめました。

puppeteer

puppetterはGoogleChromeが公式に出しているヘッドレスブラウザです。
パペティアって読むみたいです(読めませんでした)。パペット(人形)に「~の人」という意味のer、eerをつけた形みたいです。
https://github.com/GoogleChrome/puppeteer
↑↑こちらがgithubのURLです。

ブラウザ操作をコードベースで書くことができて、クリックやフォーム入力もできるのでログインが必要な画面であったとしても操作することが出来てとても便利です。実際にChromeを操作するようなものなので安心感もあります。

RailsのCapybara

調べていくうちにRailsに標準で入っているCapybaraというGemがブラウザ操作のテストをするためのものだと知りました。
puppeteerと同様にクリックやフォーム入力ができます。

何よりRailsのテストコマンドとして実行できるので環境の切り替えがコマンドをちょっと変えるだけですむのと、railsのプロジェクト内にコードを書いていくことになるのでgit管理できてページの変更に対して管理がしやすいです。

ってことでCapybaraを使用することにしました。

Capybaraを使う

テストツールとしてRspecを導入していて、Rspecのテストの中でCapybaraを使用することが出来ます。
Rspecで各要素ごとにテストを書くことが出来ます。

  • Model
  • Request
  • Controller
  • Feature
  • Mailer
  • Job
  • View
  • Routing
  • Helper

が用意されています。
公式のドキュメントです。
http://rspec.info/documentation/3.7/rspec-rails/#label-Request+Specs

今回ページ単位でのテストだったのでControllerのテストを作成すればいいのかと思いましたが、Controllerのテストは最近のversionでは非推奨でRequestに書くべきとされています。
Requestではリクエストを投げてレスポンスを受け取ることが出来ます。

ブラウザテストをしたい場合はcapybaraを導入して、Featureで行うとのことが書いてあります。
ということで今回Featureでテストを書いていきます。
本来はFeatureでテストすべき項目は目に見える部分のテストとされているので今回みたいにstatusをチェックすることやメタタグをチェックすることに対しては、Requestに書くのが正しいかもしれませんが、ページ内のリンクを抽出してアクセスしたり、今後ログインのテストなど入ってきたときにそのまま書けるので便利だと思ったのでFeatureで書いていくことにしました。

実装

/spec以下にfeatureディレクトリを作成します。
その下にspecファイルを作成していきます。

require 'rails_helper'

  RSpec.describe "top page", type: :feature do
    it "GET /" do
      visit "/"
      expect(page).to have_http_status(:success)
      expect(page.html.to_s.include?('')).to be_truthy

      all('a').map { |link| link[:href] }.reject { |url| url.nil? }.each do |url|
        visit url
        expect(page).to have_http_status(:success)
      end
    end
  end

このように実装しました。
Capybaraを使う場合に

visit "/"

のようにすればそのページにアクセスすることが出来ます。

expect(page).to have_http_status(:success)

で200が返ってくるかをチェックしています。

expect(page.html.to_s.include?('')).to be_truthy

ここではcanonicalタグが期待通りにでているかをチェックしています。

all('a').map { |link| link[:href] }.reject { |url| url.nil? }.each do |url|
    visit url
    expect(page).to have_http_status(:success)
  end

この部分で全部のリンクにアクセスして、status_codeをチェックします。

Capybaraでクリックのコマンドがありますが、元にいたページに戻るのがうまくいかなかったため、直接リンクのURLにアクセスするようにしています。
これを対象のページに対して全て実装していけば完成です。

データベースの切り替え

本来のテストでは毎回スキーマからDBを作成して定義したテストデータを作成してテストしテストごとに生成し削除という流れになります。
今回では実際のデータで行いたいので毎回生成削除というフローは適さないです。なので別のDBで行いたいです。実際のデータを使用したいといえど本番のDBはセキュリティグループでアクセス制限されているので、ローカルに本番のデータをコピーしてテストを行うようにしました。

RSpecでテストを実行する場合は

bundle exec rspec spec/

このようにしますが、

RAILS_ENV=development bundle exec rspec spec/

のように環境を指定することでテスト用に使用するDBを切り替えることが出来ます。

テストのタグ付け

先程のコマンドだと全てのテストを実行してしまします。
feature以下のテストは実際のデータを使用しますが、その他の普通のテストはテストDBを使用したいのでこれだとうまく運用できません。

これを満たすためにRspecのタグ付けの機能を使用しました。

require 'rails_helper'

RSpec.describe "top page", type: :feature, main_data: true do #タグを追加
    it "GET /", priority: :low do
      visit "/"
      expect(page).to have_http_status(:success)
      expect(page.html.to_s.include?('')).to be_truthy
 
      all('a').map { |link| link[:href] }.reject { |url| url.nil? }.each do |url|
        visit url
        expect(page).to have_http_status(:success)
      end
    end
  end

先程の雛形にこのようにタグを追加できます。

main_dataタグがtrueのもののみ実行

bundle exec rspec spec/ --tag main_data

main_dataタグがtrueではないもののみ実行

bundle exec rspec spec/ --tag ~main_data

priorityタグがlowのもののみ実行

bundle exec rspec spec/ --tag priority:low

というようにすれば指定することが出来ます。

まとめ

今回簡単にですがテストを作成しました。完成して実際に回してみたら404エラーページが何個も見つかって作ってよかったなと思っています。
今後も新しいページが出来たときにもテストを定義することで簡単にチェックできて安定性に加えて開発効率の向上に繋がりました。
今後はユニットテストの方もしっかり作っていきたいと思っています。