CucumberとJenkinsを使って、PHPなどで作られたリモートのWebアプリの受け入れテストを自動で行う
WordPressのようにPHPなどでできたWebサイトの振る舞いを自動でテストしたいと思い、仕組みを作ることにしました。
きっかけは、設定が間違ってて、トップページは正常に表示されているにも関わらず、個別のエントリページではエラーになってることがあったためです。
別にWordPressに限った話ではなく、Pythonとかで開発してても必要になる話なので、簡単なところから始めてみようというわけです。
なお、Rubyの世界をあまりわかってないので、Ruby周りの勉強を兼ねてます。間違っていたら教えてもらえるとありがたいです。
やりたいこと
- ページがアクセスできるかチェックしたい。
- なるべくサーバーとか立てずに簡単にやりたい。
- 将来的にはちゃんとしたブラウザでの動作チェックに応用したい。
構成
今回の受け入れテストは、以下のライブラリを使って実現します。
- Cucumber
- Capybara
- Capybara-Mechanize
Capybaraについて
- Webアプリの振る舞いをテストするためのライブラリ。
- かつてはWebratが主流だったけど、JavaScriptが使えないのでCapybaraに移行してるみたい。*1
- ドライバ(Webアクセスする部分)を色々切り替えることができ、Selenium, Capybara-Webkitなどがある。
- Rackアプリのテスト専用のrack_test(実際のHTTP通信をしない)がデフォルトのドライバ。
- 今回は外部サイトをテストでき、簡単に使えるCapybara-Mechanizeを使う。
Cucumberによる受け入れテストを実現するための手順
とりあえずJenkinsは置いておき、開発環境の上でテストを動かします。
1. Gemfileを作成する
$ bundle init
を実行するとカレントディレクトリに Gemfile ができるので、以下のように書き加えます。
# A sample Gemfile source "https://rubygems.org" # gem "rails" gem "cucumber" gem "rspec" gem "capybara-mechanize"
2. gemをインストールする
$ bundle install --path=vender/bundle
pathを指定しない場合はシステム環境にインストールされるので注意。
3. cucumberのディレクトリ構成を作る
$ mkdir features $ mkdir features/step_definitions $ mkdir features/support
4. cucumberの設定をする
features/support/env.rb を以下の内容で作成します。ここではテスト対象としてGoogleを指定していますが、本来は自分のWebサイトを指定します。
#coding: utf-8 require 'capybara' require 'capybara/cucumber' require 'capybara/mechanize' require 'rspec' # テスト対象のサイト # ドライバがmechanizeの場合、他のサイトにもアクセスできる。 Capybara.app_host = 'http://google.co.jp' # デフォルトで使われるドライバ # これを指定しないと、rack_testが使われるため # PHPなどRack以外のアプリはテストできない。 Capybara.default_driver = :mechanize Before do # タイムアウトの設定(おそらくmechanizeでしか有効でない) # 時間がかかりすぎるときはエラーにする # コネクションを開くまでのタイムアウト秒数 Capybara.current_session.driver.browser.agent.open_timeout = 1 # データ読み出しのタイムアウト秒数 Capybara.current_session.driver.browser.agent.read_timeout = 1 end
env.rbはテストに必要な設定をするファイルです。*4
読み込みに時間がかかるときはエラーにしたいので、タイムアウトの設定*5を探したのですが見つからなかったため、Mechanizeオブジェクトのタイムアウトを直接設定しています。いい方法をご存じの方は教えていただけると嬉しいです。
5. cucumberを実行する
$ bundle exec cucumber 0 scenarios 0 steps 0m0.000s
まだなにもテストケースを作っていないので、このように表示されます。
ちなみにbundle exec はコマンドを実行する際にローカルのgemを使うためのコマンドです。
6. featureを書く
features/browsing.featureを以下の内容で作成します。
# language: en Feature: Search Scenario: Search for orangain Given I am on the "/" page of Google When I search for "orangain" Then I should get response with content-type "text/html" And I should see "orangain - Google 検索" in the title bar And I should see "かと (orangain) on Twitter" in the page
featureには期待される振る舞いを(一定の規則に沿った)自然文で記述します。
featureは日本語でも書けますが、英語のほうが入力が楽そうなので英語にしました。
なお、Capybara-MechanizeのREADMEには@mechanizeを書くとありますが、env.rbで設定しているので不要です。
7. cucumberを実行する
$ bundle exec cucumber # language: en Feature: Search Scenario: Search for orangain # features/search.feature:4 Given I am on the "/" page of Google # features/search.feature:5 When I search for "orangain" # features/search.feature:6 Then I should get response with content-type "text/html" # features/search.feature:7 And I should see "orangain - Google 検索" in the title bar # features/search.feature:8 And I should see "かと (orangain) on Twitter" in the page # features/search.feature:9 1 scenario (1 undefined) 5 steps (5 undefined) 0m0.007s You can implement step definitions for undefined steps with these snippets: Given /^I am on the "(.*?)" page of Google$/ do |arg1| pending # express the regexp above with the code you wish you had end When /^I search for "(.*?)"$/ do |arg1| pending # express the regexp above with the code you wish you had end Then /^I should get response with content\-type "(.*?)"$/ do |arg1| pending # express the regexp above with the code you wish you had end Then /^I should see "(.*?)" in the title bar$/ do |arg1| pending # express the regexp above with the code you wish you had end Then /^I should see "(.*?)" in the page$/ do |arg1| pending # express the regexp above with the code you wish you had end
こんどはスニペットが表示されます。便利。
8. step_definitionを作成する
features/step_definitions/search_steps.rb に先ほど表示されたスニペットを貼りつけて保存します。
#coding: utf-8 Given /^I am on the "(.*?)" page of Google$/ do |arg1| pending # express the regexp above with the code you wish you had end When /^I search for "(.*?)"$/ do |arg1| pending # express the regexp above with the code you wish you had end Then /^I should get response with content\-type "(.*?)"$/ do |arg1| pending # express the regexp above with the code you wish you had end Then /^I should see "(.*?)" in the title bar$/ do |arg1| pending # express the regexp above with the code you wish you had end Then /^I should see "(.*?)" in the page$/ do |arg1| pending # express the regexp above with the code you wish you had end
step_definitionsには、featureの自然文に相当するコード(step_definition)を書きます。正規表現で一致したstep_definitionが使われるというわけです。
9. cucumberを実行する
$ bundle exec cucumber # language: en Feature: Search Scenario: Search for orangain # features/search.feature:4 Given I am on the "/" page of Google # features/step_definitions/search_steps.rb:3 TODO (Cucumber::Pending) ./features/step_definitions/search_steps.rb:4:in `/^I am on the "(.*?)" page of Google$/' features/search.feature:5:in `Given I am on the "/" page of Google' When I search for "orangain" # features/step_definitions/search_steps.rb:7 Then I should get response with content-type "text/html" # features/step_definitions/search_steps.rb:11 And I should see "orangain - Google 検索" in the title bar # features/step_definitions/search_steps.rb:15 And I should see "かと (orangain) on Twitter" in the page # features/step_definitions/search_steps.rb:19 1 scenario (1 pending) 5 steps (4 skipped, 1 pending) 0m0.005s
今度はpendingって表示されます。便利。
10. step_definitionの中身を書く
features/step_definitions/search_steps.rb の pending となっていたところを実装します。
#coding: utf-8 Given /^I am on the "(.*?)" page of Google$/ do |url| visit url end When /^I search for "(.*?)"$/ do |query| fill_in 'q', :with => query click_button 'Google 検索' end Then /^I should get response with content\-type "(.*?)"$/ do |content_type| page.response_headers['Content-Type'].should match('^' + Regexp.escape(content_type)) end Then /^I should see "(.*?)" in the title bar$/ do |title| page.should have_selector('title', :text => title) end Then /^I should see "(.*?)" in the page$/ do |content| page.should have_content(content) end
should っていう不思議な感じの書き方は RSpec のものらしいです。
11. cucumberを実行する
$ bundle exec cucumber # language: en Feature: Search Scenario: Search for orangain # features/search.feature:4 Given I am on the "/" page of Google # features/step_definitions/search_steps.rb:3 When I search for "orangain" # features/step_definitions/search_steps.rb:7 Then I should get response with content-type "text/html" # features/step_definitions/search_steps.rb:12 And I should see "orangain - Google 検索" in the title bar # features/step_definitions/search_steps.rb:16 And I should see "かと (orangain) on Twitter" in the page # features/step_definitions/search_steps.rb:20 1 scenario (1 passed) 5 steps (5 passed) 0m0.950s
成功したはずです。
もちろん様々な外部要因*6によって失敗するかもしれませんが、その場合は適当に直してみてください。
ここまでのソースコードは orangain/testing_remote_webapps · GitHub にあります。
テストをJenkinsで自動化する手順
続いて、上で作ったテストをJenkinsに自動実行させます。
1. RVMプラグインをインストールする
Jenkinsの管理画面からRVM Pluginをインストールします。これは、RVMの環境でビルドを実行するためのプラグインで、RVM自体も自動でインストールしてくれます。
今回、Gemの管理はBundlerで行っているので、rbenv で十分な気がしましたが、rbenv pluginよりRVMプラグインのほうが実績があったので、こちらを選択しました。
2. 必要なパッケージをインストールする
RVMのインストール、Rubyのコンパイル、Nokogiriのコンパイルなどで色々足りないと怒られるので、先に以下のパッケージをインストールしておきます。
sudo apt-get install curl sudo apt-get install zlib1g-dev sudo apt-get install libssl-dev sudo apt-get install libxml2-dev sudo apt-get install libxslt1-dev
3. Jenkinsのジョブを作成する
以下の設定をしたジョブを作成します。
プロジェクト名 | なにか適当に入力します。 |
---|---|
ソースコード管理システム | Gitを選択し、Repository URLに以下のURLを入力します。
https://github.com/orangain/testing_remote_webapps.git |
ビルド環境 | Run the build in a RVM-managed environmentにチェックを付け、Implementationに1.9.3 と入力します。 |
ビルド | 「シェルの実行」を追加し、以下のコマンドを入力します。
gem install bundler bundle install --path=vender/bundle bundle exec cucumber --format junit --out reports |
ビルド後の処理 | 「 JUnitテスト結果の集計」を追加し、テスト結果XMLにreports/*.xml と入力します。 |
詳しくは設定画面のキャプチャをご覧ください。
おまけ:専用ツールとの比較
後から知りましたが、Canoo WebTestのような専用ツールでやれば良かったかもしれません。ただ、今回作った仕組みは以下の2点に意味があると思います。
- Cucumberのテストケース(feature)は自然文で書かれていて、非エンジニアにも読みやすい。
- Capybaraは、シミュレータでのテストから本物のブラウザでのテストに変更したくなったときに、ドライバを変更するだけで対応できる。
環境
この記事は以下の環境に基づいています。
開発環境
- Mac OS X Lion 10.7.5
- ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin11.0]
- gem 1.3.6
自動ビルド環境
Gem
- bundler (1.2.1)
- capybara (1.1.3)
- capybara-mechanize (0.3.0)
- cucumber (1.2.1)
*1:The Ruby Toolbox - Browser testingって初めて知ったけど便利。
*2:HTTPのヘッダのテストとかできるのかな?
*3:JavaScriptのテストをするならCapybara-Webkitがいいらしい。
*4:ドライバを自由に使い分けたい場合は、素晴らしいenv.rbを公開してくださってる方がいたので、使わせてもらうと良いかもしれません。Testing remote (PHP) websites with Capybara, Cucumber, Mechanize, Selenium 2 Webdriver … and SauceLabs | Otaqui.com
*5:Capybara.default_wait_timeはそれっぽい名前ですが、Ajaxの処理を待つ時間なので関係有りません。
*6:GoogleのUIが変わったり、Twitterのタイトルが変わったり、検索順位が変わったり、インターネットに繋がってなかったり。