【連載】Railsで目指せ、情熱エンジニア
第8回 実例で学ぶRailsアプリのテスト方法
2011/12/22
前回はRailsで使われるテストフレームワークをご紹介しました。今回は具体的なWebアプリを例に、簡単なテストを使ったリファクタリングについて解説します。
- - PR -
インテグレーションテストのために「Cucumber」を利用する
前回は、Railsで使われるテストフレームワークをご紹介しました。今回から、いよいよ実際のテストを書きます。ただ書くだけでは物足りないので、前々回の連載で指摘したコードレビューの結果から、リファクタリングの候補をリストアップし、テストを書きながら1つ1つつぶしていきましょう。
- bitlyの設定はサーバの立ち上げ時にするべき
- 重複したコード
- 本来モデルにあるべきロジックがコントローラにある
- 不必要な構文「then」など
まずは1の、「bitlyの設定」のロジックを変更したいと思います。この部分です。
class ItemsController < ApplicationController
conf = APP_CONFIG["bitly"]
@@bitly = Bitly.new(conf["username"], conf["apikey"])
ただ、普通にリファクタリングによってコードを変更しても、ちゃんと機能するかどうかが不安なので、先にインテグレーションテストを設定しましょう。ここではRails開発者の間で人気の高いCucumber(キューカンバー)を利用します。
Cucumberの設定はcucumber-railsを使うと簡単にできるようです。まずGemfileに以下を追加します。
group :test do
gem "rspec-rails"
gem 'cucumber-rails'
gem 'capybara'
gem 'database_cleaner'
end
そして「bundle install」としてgemをインストールした後に、以下のようにrspecとcucumberをそれぞれインストールします。
rails g rspec:install
rails g cucumber:install --rspec --capybara
その後にgeneratorを使うと、以下のような「feature」の雛形が作られます。
ruby script/rails generate cucumber:feature
Feature: Manage items
In order to [goal]
[stakeholder]
wants [behaviour]
Scenario: Register new item
Given I am on the new item page
And I press "Create"
では、実際のfeatureを書いてみましょう。featureというのは機能のことで、どういう機能があって、誰が何ができるかを自然言語(ここでは英語ですが、日本語も使えます)で記述します。まず最初に、ログイン状態であればitemが追加登録できるというfeatureの記述例です。
Feature: Manage items
In order to register new item
As a user
I want to add new item
Scenario: Register new item
Given I am logged in as "Bob"
And I am on the root page
And I follow "Users"
And I follow "Bob"
And I fill in "http://www.google.com" for "new_item"
And I press "Add"
Then I should see "Created an item"
この状態でcucumberを走らせると、以下のステップを作る必要があると警告がでてきます。
[worklista (29df2bb...)]$ rake cucumber
1 scenario (1 undefined)
7 steps (5 skipped, 2 undefined)
0m0.026s
You can implement step definitions for undefined steps with these snippets:
Given /^I am logged in as "([^"]*)"$/ do |arg1|
pending # express the regexp above with the code you wish you had
end
逆に言うとそれ以外のステップはすでに存在するということです。実は「rails g cucumber:install」としたときに、以下のファイルが作られています。
features/step_definitions/web_steps.rb
features/support/env.rb
features/support/paths.rb
lib/tasks/cucumber.rake
script/cucumber
その中の「web_steps.rb」ファイルを覗いてみると以下のようなステップが作られています。
module WithinHelpers
def with_scope(locator)
locator ? within(locator) { yield } : yield
end
end
World(WithinHelpers)
Given /^(?:|I )am on (.+)$/ do |page_name|
visit path_to(page_name)
end
When /^(?:|I )go to (.+)$/ do |page_name|
visit path_to(page_name)
end
When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
with_scope(selector) do
click_button(button)
end
end
When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
with_scope(selector) do
click_link(link)
end
end
では、未定義のステップをfeatures/step_definitions.rbに追加します。ユーザーがログインするフローを記述します。以下の通りです。
Given /^I am logged in as "([^"]*)"$/ do |arg1|
Given %{I am on the home page}
And %{I follow "Sign up"}
And %{I fill in "bob@example.com" for "user_email"}
And %{I fill in "Bob" for "user_username"}
And %{I fill in "testforbob" for "user_password"}
And %{I fill in "testforbob" for "user_password_confirmation"}
And %{I fill in "dummy_code" for "user_invite_code"}
And %{I press "Sign up"}
And %{I follow "Login"}
And %{I fill in "bob@example.com" for "user_email"}
And %{I fill in "testforbob" for "user_password"}
And %{I press "Sign in"}
end
ログインの詳細は今回のfeatureではあまり重要視していない部分なので、ここでは1つのステップにまとめてみました。
さて、これで以下のように、ちゃんとfeatureがパスするはずです。
[worklista (1cc509a...)]$ rake cucumber
[2011-01-03 21:35:51] INFO WEBrick 1.3.1
[2011-01-03 21:35:51] INFO ruby 1.9.2 (2010-08-18) [x86_64-darwin10.4.0]
[2011-01-03 21:35:51] INFO WEBrick::HTTPServer#start: pid=26542 port=9887
1 scenario (1 passed)
8 steps (8 passed)
0m15.348s
実は今回のコミットではfeatures/support/env.rbに以下のような設定も付け加えておきました。
Capybara.javascript_driver = :selenium
Cucumberは、通常はWebratというブラウザの動きをシミュレートするライブラリを使います。しかしながらシミュレータではJavaScriptの動作などをテストすることができません。そこでCapybaraというライブラリを使います。Capybaraを使うと、実際にブラウザを立ち上げて自動テストするためのさまざまなツールと簡単に統合することができます。ここではSeleniumというツールを使っています。こうすることで「@javascript」が付いたfeatureを、実際にブラウザ上でテストすることができるようになります。以下のスクリーンキャストは、実際にブラウザ上でのテストが走っている模様を再現したものです。
今回はブラウザとしてFirefoxを使っていますが、他のブラウザに切り替えることでクロスブラウザテストが可能です。実際のブラウザを使った自動テストはシミュレート版より遅いので、必要なテストにだけ使ったほうが良いですが、実際にコードを書かないステークホルダー(実際にシステムを使うエンドユーザーやプロジェクトに出資しているビジネスオーナーなど)や、プロダクトオーナーに自動化テストの威力をデモしたいときには、Sleniumのようにブラウザで自動で動くものを見せると結構インパクトがあるのでお勧めです。
「グリーン、レッド、グリーン、リファクター」のリズムで
通常のテスト駆動開発では「レッド、グリーン、リファクター」のリズムで行うのが良いと言われます。レッドはテストケースが失敗していることを示し、グリーンはテストがパスしたことを示します。
テスト駆動開発では、まず最初にテストを書き、このテストが失敗するのを確認します(レッド)。次に、このテストをパスさせるための最低限のコードを書きます(グリーン)。最後に、書かれたコードをリファクタリングすることで「機能追加」と「コードをきれいにする作業」を、順を追って行います。
既存のコードに後からテストを追加する場合、この手法は一見使えないように思えますが、何とかするやり方はあります。先ほどのテストがグリーンなのを確認した後、リファクタリングする予定の箇所を一度削除してみましょう。
[worklista (8540c64...)]$ git co 8540c64f2a7ae4f0d4e7fdd3494bed7cce2ca4c2
- conf = APP_CONFIG["bitly"]
- @@bitly = Bitly.new(conf["username"], conf["apikey"])
その状態でテストを走らせると、もちろんレッド(失敗)になりますよね。
[worklista (refactoring)]$ rake cucumber
(::) failed steps (::)
uninitialized class variable @@bitly in ItemsController (NameError)
./app/controllers/items_controller.rb:111:in `populate_retweet'
./app/controllers/items_controller.rb:89:in `populate'
./app/controllers/items_controller.rb:31:in `create'
<internal:prelude>:10:in `synchronize'
./features/step_definitions/web_steps.rb:29:in `block (2 levels) in <top (required)>'
./features/step_definitions/web_steps.rb:14:in `with_scope'
./features/step_definitions/web_steps.rb:28:in `/^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/'
features/manage_items.feature:12:in `And I press "Add"'
Failing Scenarios:
cucumber features/manage_items.feature:6 # Scenario: Register new item
1 scenario (1 failed)
8 steps (1 failed, 2 skipped, 5 passed)
その状態で、今度はbitlyの設定をconfig.rbに移行します。
[worklista (2012b30...)]$ git show 2012b30d483d17cb89978c8f200895089445d180
commit 2012b30d483d17cb89978c8f200895089445d180
Author: Makoto Inoue <inouemak@googlemail.com>
Date: Sun Dec 26 22:49:19 2010 +0000
Moved Bitly initializer to config.rb
diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb
index a002af4..3312722 100644
--- a/app/controllers/items_controller.rb
+++ b/app/controllers/items_controller.rb
@@ -5,7 +5,6 @@ require 'resolv-replace'
class ItemsController < ApplicationController
before_filter :authorise_as_owner
-
def create
@user = User.find(params[:user_id])
@@ -108,7 +107,7 @@ private
end
def populate_retweet(item)
- url = @@bitly.shorten(item.url)
+ url = BITLY.shorten(item.url)
item.bitly_url = url.short_url
item.retweet = url.global_clicks
end
diff --git a/config/initializers/config.rb b/config/initializers/config.rb
new file mode 100644
index 0000000..d740917
--- /dev/null
+++ b/config/initializers/config.rb
@@ -0,0 +1,6 @@
+APP_CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")[Rails.env]
+
+require 'bitly'
+Bitly.use_api_version_3
+conf = APP_CONFIG["bitly"]
+BITLY = Bitly.new(conf["username"], conf["apikey"])
config.rbの中ではItemsControllerのクラス変数(@@bitly)は設定できないので、コンスタント(BITLY)に変えてみました。
ではこの状態でもう一度テストを走らせてみましょう。
git co 2012b30d483d17cb89978c8f200895089445d180
[worklista (2012b30...)]$ rake cucumber
........
1 scenario (1 passed)
8 steps (8 passed)
ちゃんとグリーンがでましたね。
このように「グリーン、レッド、グリーン、リファクター」の手順を踏むことで、現在変更している箇所をカバーするテストがあるのをちゃんと確認しながらリファクターをすることができました。
次回はコントローラのテストに移っていきます。
| 連載の前回へ | 1/1 |
| Index | |
| 実例で学ぶRailsアプリのテスト方法 | |
| Page1 インテグレーションテストのために「Cucumber」を利用する 「グリーン、レッド、グリーン、リファクター」のリズムで | |
TechTargetジャパン
- 派生型でもっと便利にデータを扱う (2012/4/26)
基本型を組み合せて使える派生型を学びます。派生型には構造体、共用体、配列などがあります - 実例で学ぶRailsアプリのテスト方法 (2011/12/22)
具体的なWebアプリを例に簡単なテストを使ったリファクタリングについ
て解説する - Railsの人気テストフレームワーク6選! (2011/8/18)
今回からテストを使ったリファクタリングを解説する。まずはRailsで人
気のあるテストフレームワークをいくつか紹介する - ActiveRecordの更新系操作 (2011/6/27)
Railsのモデル層を担当するActiveRecordを使った登録、更新、削除
など、更新系の機能を中心に見ていきます
|
|

