レガシーでテストのないプロジェクトのメンテナンスはつらいものです。テストを書こうにもそもそもテストできるような設計になっておらず、ビューとロジックが分離されていない古い PHP なんかは、なかなかテストを書き始め辛いところもあります。そこで実際にブラウザを起動して自動テストを進めることが出来る capybara を使って仕様化テストをやってみようという試みです。実際に会社では PHP で出来たレガシーなシステムに対して capybara でテストを作ってまわしています。
capybara というと ruby で開発されている有名なウェブアプリケーションテストフレームワークですが ruby でできているからといって ruby のプロジェクトにしか使えないわけではありません。capybara にはテストを実行する際に利用するドライバを指定できて、それには selenium や poltergeist も対応しています。これらのドライバを使ってブラウザを起動して (polgergeist の場合はヘッドレスなので実際にブラウザは起動せずにオンメモリで) 本当にウェブアプリケーションにアクセスしてテストを実行することができます。
この記事では Rails 以外のたとえば PHP や JSP などで出来てるウェブアプリケーションに対して selenium ドライバを使って capybara をやってみたいと思います。
※仕様化テストとは
現在の仕様を元にテストが通るようにテストを書いていきます。これを書いておくことによって、ある箇所のソースコードの変更によって全然別の箇所で影響があって意図しない動作をして不具合になってしまった、なんてことを未然に防ぐことができます。レガシーなプロジェクトのメンテナンスでも心に余裕がもてます。
ruby をインストールする
普段から ruby を使ってる人は既に ruby はインストールされていると思うので、その場合はこのステップは飛ばしましょう。
ruby 以外のプロジェクトで使うにしても capybara 自身が ruby がないと動かないので何はともあれ ruby のインストールから始めます。ruby のインストールは rbenv を使うと楽でね。元々 ruby のバージョン切り替えのためのツールなのですが、別に ruby を複数バージョンいれないとしてもこのツール自体シンプルで使いやすいし、今後 ruby のバージョンが上がってもバージョンアップが簡単なのでとりあえず入れておくといいと思います。
この辺を見れば簡単に ruby のインストールまでできます。
ruby がインストールできたら、あとで使うので bundler という gem をインストールしておきます。
$ gem install bundler
Firefox をインストールする
今回使うドライバ selenium では、デフォルトで起動できるブラウザが Firefox なのであらかじめインストールしておきます。既にインストールされている場合はこのステップは飛ばします。
Gemfile を準備する
capybara
だけじゃなくて rspec
もあった方がいいので一緒にインストールします。また capybara ではドライバとして selenium を使うので selenium-webdriver
も一緒にいれておきます。
mysql2
は DB に繋いで状態を確認するために使う分なので DB が PostgreSQL の場合は pg
だし SQLite の場合は sqlite3
を入れます。activerecord
やら activesupport
やらはテストする時にあった方がなにかと便利なので一緒にいれておきます。
あと awesome_print
と pry
は完全にデバッグ用途なのであってもなくてもいいと思います。
source 'https://rubygems.org'
gem 'capybara'
gem 'rspec'
gem 'selenium-webdriver'
gem 'mysql2'
gem 'activerecord'
gem 'activesupport'
gem 'awesome_print'
gem 'pry'
これをプロジェクトのルートディレクトリなどに保存します。
保存したらターミナルで、そのディレクトリまで移動してから gem をインストールしておきます。
$ cd /path/to/directory
$ bundle install
データベースへの接続設定を準備する
次に既存システムが利用するデータベースへの接続設定を準備します。gem に activerecord
を入れたので DB にはそれを使って接続しますので database.yml 形式で設定ファイルを準備しておきます。
development:
adapter: mysql2
database: your_database_name
host: localhost
username: your_username
password: your_password
pool: 5
timeout: 5000
本番環境に対してはテストしないと思うので、開発環境への接続のみ定義していれば十分です。これもどこかに保存しておきます。
spec_helper の準備
最後に以下のようなファイルを準備します。
# 必要ファイルの読み込み
require 'rubygems'
require 'awesome_print'
require 'capybara/rspec'
require 'selenium-webdriver'
require 'active_record'
require 'active_support/core_ext'
# DBへの接続
dbconfig = YAML::load(File.open('database.yml'))
ActiveRecord::Base.establish_connection(dbconfig['development'])
# ajaxの通信などで待ちが発生する場合の最大待ち時間
Capybara.default_wait_time = 30
# seleniumを使う
Capybara.default_driver = :selenium
RSpec.configure do |config|
config.include Capybara::DSL
end
これは rspec 用のヘルパーで rspec 関係のファイルは spec/
以下に配置するのが慣例となってるので spec/spec_helper.rb
あたりに保存します。
仕様に沿ってテストを書く
あとはシステムの既存仕様に沿ってテストを書いていくだけです。rspec を使った capybara の書き方についてはいろんなサイトで解説がされていると思うので、ここではあえて深く説明しませんが、たとえばこんな風に書きます。(※このテストシナリオは完全なフィクションです)
spec/order_spec.rb
などに保存してみます。
require 'spec_helper'
describe '申込フロー', type: :feature, js: true do
before do
# 開発環境の申込画面にアクセスする
visit 'http://localhost:8000/order/order.php'
end
describe 'サービスを申し込む' do
let(:user_id) { 'tkengo' }
let(:nick_name) { 'tkengo-chop' }
before do
fill_in 'user_id', user_id
fill_in 'nick_name', nick_name
select plan, from: 'plan_id'
click_on '申し込む'
@order = Order.where(user_id: user_id)
end
it '申込みデータができていること' do
expect(@order.nick_name).to eq nick_name
end
it '申込完了画面が表示されている' do
expect(page).to have_title 'お申し込み完了'
expect(page).to have_text "#{plan_id}のお申し込みが完了しました!"
end
context '無料プランの場合' do
let(:plan_id) { '無料プラン' }
it '無料なので料金は0円であること' do
expect(@order.charge).to eq 0
end
end
context 'プランAの場合' do
let(:plan_id) { 'プランA' }
it '料金は1000円であること' do
expect(@order.charge).to eq 1000
end
end
context 'プランBの場合' do
let(:plan_id) { 'プランB' }
it '料金は2000円であること' do
expect(@order.charge).to eq 2000
end
it 'オプションプランのデータができていること' do
expect(@order.option).not_to be_nil
end
end
end
end
テストを実行する
テストを書いたらあとはそれを実行するだけです。
$ bundle exec rspec spec/order_spec.rb
勝手に Firefox が立ち上がってうにうに動いてテストしてくれるはずです。
自分の手元で回すだけではなく、最終的には CI に登録して定期的にテストを実行してくれるような環境を整えることができればなお良いですね。CI については Jenkins でのやり方を過去に書いたことがあるので、そちらを参考にやってみるのも良いかもしれません。
Rails プロジェクトで Jenkins を使って CI してみる
タイトルに Rails とついてますが中身は rspec を使ってテストするための Jenkins のインストールや設定方法の解説が主なので、今回のような場合でも使えると思います。
おわり
こういうものを準備してテストを回すことを意識していれば、例えばビューとロジックが混じっているかもしれないカオス気味な order.php
の挙動を少し変更しようとして、意図せず if 文の条件に使われているグローバル変数が書き換わって本来無料プランなのに料金が発生したりすると、仕様通りに書いたこのテストが落ちるはずなのでその時に気付けます。
最初のテストを書くことに時間はかかると思いますが1度書いておけば、しばらくは使い続けることができます。もちろん仕様自体が途中で変わったらこのテストも修正しないといけないですが、一度出来て公開されているレガシーシステムの仕様が途中で変わることはそうそう無いと思います。
1週間に1個とかのスローペースでもいいのでまずは作り始めてみると意外と楽しいもんです。