けんごのお屋敷

2014-01-28

レガシープロジェクトで ruby の capybara を使って仕様化テストをやってみる

レガシーでテストのないプロジェクトのメンテナンスはつらいものです。テストを書こうにもそもそもテストできるような設計になっておらず、ビューとロジックが分離されていない古い PHP なんかは、なかなかテストを書き始め辛いところもあります。そこで実際にブラウザを起動して自動テストを進めることが出来る capybara を使って仕様化テストをやってみようという試みです。実際に会社では PHP で出来たレガシーなシステムに対して capybara でテストを作ってまわしています。

capybara というと ruby で開発されている有名なウェブアプリケーションテストフレームワークですが ruby でできているからといって ruby のプロジェクトにしか使えないわけではありません。capybara にはテストを実行する際に利用するドライバを指定できて、それには seleniumpoltergeist も対応しています。これらのドライバを使ってブラウザを起動して (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_printpry は完全にデバッグ用途なのであってもなくてもいいと思います。

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個とかのスローペースでもいいのでまずは作り始めてみると意外と楽しいもんです。

  • このエントリーをはてなブックマークに追加