Rails + rcov でテストカバレッジを調べる

Ruby(とRails)を担当している石原です。
ソーシャル「OSを入れた後にインストールする10のアプリケーション」(仮) を作る過程をレポートしています。
これまでのエントリーはこちら ↓
- つくるぶガイドブログ: Ruby on Rails を使ってひとりでサービスを作ってみよう
- つくるぶガイドブログ: ひとりサービスの雛型をつくる(リキッドレイアウト、GetText、Acts as Authenticated)
- つくるぶガイドブログ: Rails で楽々ソーシャルブックマークの仕組みを作る
- つくるぶガイドブログ: Rails プラグイン acts_as_taggable_redux でタグクラウドを作ろう
- つくるぶガイドブログ: ドラッグアンドドロップで並べ替え(Rails + Ajax)
今回は少し話題を変え、テストとカバレッジツールを取り上げます。
アプリケーションにバグがないかどうかを調べるのがテストなわけですが、フォームにテキストを入力したり、ボタンをクリックして実際にアプリケーションを触って確かめるのが手動テストです。でも、手動テストは面倒なものですから、何度も何度も繰り返したりすることはできません。
操作と、その操作の結果が正しいかどうかの判定を自動でおこなうプログラムを書けば、テストを何度でも繰り返し実行可能になります。これがテストコードを書くということで、Rails で「テスト」といった場合は一般的にはこちらを指します。
テストコードを書くことは面倒なことです。書けば確かに品質の確保につながりますが、それをやることによって直接の開発が進むわけではないので、なかなか「テストコードを書こう」という気が起きないものだと思います。
この点、Rails にはあらかじめテストの仕組みが用意されており、テストコードの雛型まで用意されているので、最初の一歩を非常にスムーズに踏み出せるようになっています。
rake( test)でテストを実行する
では、さっそく 10best でテストを実行してみましょう。
rake
これは rake test を実行するのと同じです。rake を引数なしで実行するだけでテストがおこなえるというのも、テストの敷居を下げようという Rails の思想の現れでしょう。
テストを実行した結果、エラーが大量に表示されました。
エラーメッセージを良く見ると、いたるところで
Mysql::Error: Unknown database 'alpha_test'
と表示されているのが目につきます。
テストには開発用データベースとは別のデータベース alpha_test が必要なのです。
mysql -u root
mysql> create database alpha_test;
で alpha_test を作成し、もう一度 rake を実行します。
エラーは幾分かは減りましたが、Failure というキーワードとともに、まだたくさんのメッセージが表示されます。
Failure とは、テストコードをエラーなしで実行はできているのですが、想定していた結果とは違う、つまりテストが失敗していることを意味します。
実は、Scaffold でサービスの雛型を作ったときや、Acts As Authenticated プラグインを導入したときに自動的にテストコードが作成されていたのです。これらが、作成された時点ではエラーや Failure なしで実行可能だったのが、これまでアプリケーションを作成していく過程で様々な修正を施したために、元のままではいくつかの部分がテスト失敗となってしまったのです。
エラーまたは Failure の部分すべてを取り上げるのは大変なので、ここでは一箇所だけ修正し、ほかの部分はとりあえずコメントアウトして見かけ上テストが成功しているように見えるようにします。
1) Error: test_should_login_with_cookie(AccountControllerTest): ActionController::MissingTemplate: Missing template /home/junya/rails/10best/config/../app/views/account/index.rhtml
と表示されている部分を見ていきます。
エラーメッセージは、「app/views/account/index.html がみつからない」と伝えていますが、account コントローラーの index アクションは削除しました。
ここは、現状のアプリケーションの構成にテストコードをあわせる必要があります。
上記エラーメッセージ部分以下を見ていくと、
2) Failure: test_create(BookmarksControllerTest)
が始まる直前、
./test/functional/account_controller_test.rb:97:in `test_should_login_with_cookie'
とあります。エラーが test/functional/account_controller_test.rb の 97 行目で発生していることを示しています。該当部分は以下の通り。
== test/functional/account_controller_test.rb ==
def test_should_login_with_cookie
users(:quentin).remember_me
@request.cookies["auth_token"] = cookie_for(:quentin)
get :index
assert @controller.send(:logged_in?)
end
このテストコードは、cookie 情報をつかってログインする処理が正常におこなわれているかをテストしています。
users(:quentin).remember_me
@request.cookies["auth_token"] = cookie_for(:quentin)
で quentin ユーザーのための cookie を作成する、といった前処理をおこなっていますが、次の
get :index
で index アクションを get で呼び出す、つまり手動テストなら /account/index という URL にアクセスしようとしています。しかし index アクションは削除し、代わりに /default/index をトップページにしました。つまりテストするべきコントローラーが変わってしまっていたのです。
テストコードはコントローラーごとに分かれたファイルに記述されています。このため、上記 test_should_login_with_cookie は test/functional/account_controller_test.rb から test/functional/default_controller_test.rb に移さなくてはなりません。
コードが正常に実行できるよう、fixtures 文や使用している protected メソッドを加え、default_controller_test.rb は以下のように修正します。
require File.dirname(__FILE__) + '/../test_helper'
require 'default_controller'
# Re-raise errors caught by the controller.
class DefaultController; def rescue_action(e) raise e end; end
class DefaultControllerTest < Test::Unit::TestCase
fixtures :users
def setup
@controller = DefaultController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
# Replace this with your real tests.
def test_truth
assert true
end
def test_should_login_with_cookie
users(:quentin).remember_me
@request.cookies["auth_token"] = cookie_for(:quentin)
get :index
assert @controller.send(:logged_in?)
end
protected
def create_user(options = {})
post :signup, :user => { :login => 'quire', :email => 'quire@example.com',
:password => 'quire', :password_confirmation => 'quire' }.merge(options)
end
def auth_token(token)
CGI::Cookie.new('name' => 'auth_token', 'value' => token)
end
def cookie_for(user)
auth_token users(user).remember_token
end
end
そのほかの、テストが通っていない部分をコメントアウトし、rake を実行した結果が、下記の通り、0 failures, 0errors の状態になるようにしておきます。
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started ................. Finished in 0.399946 seconds. 17 tests, 29 assertions, 0 failures, 0 errors
いささか乱暴な方法でしたが、テストを最初の時点で 0 エラーの状態にしておくことは大切です。この後は順次コメントアウトした部分を見ていき、テストが通るように書き換えてはコメントアウトを外していきます。あるいは機能を追加していくごとに、追加した機能をテストするテストコードを書き加えていきます。
いずれにしても、rake test を実行したときに、常に 0 エラーの状態をキープするようにしていけば、新たに追加した機能あるいは施した修正が知らない間にほかの部分を壊してしまった時に、今まで出ていなかったエラーが出ることで、それを発見することができるのです。
ひとりでサービスを開発している場合、テスターがいるようなある程度の規模の開発チームとは違って、どうしてもバグを見落としがちになってしまいます。
大規模な組織によって開発されたアプリケーションと同等の品質は求められていない、と言えるかもしれませんが、でも、テストコードをきちんと書くことによって同等、あるいはそれ以上の品質を保つことができると思います。
rcov でテストカバレッジを調べる
テストコードを書いても、アプリケーションのごく一部の機能しかテストしていなければ、「良くテストされている」状態とは言えません。テストコードがカバーしていない部分にたくさんバグが潜んでいる可能性がありますから。
テストコードがアプリケーションのどの程度をカバーしているかを測定するのがカバレッジツールです。アプリケーションの本来のソースコードがテストコードによって実行された部分を、カバーした部分とみなします。つまり100行あるコードの半分の50行がテストコードによって実行されれば、カバー率は 50% とみなされるのです。
Ruby のコードカバレッジツール rcov を使用します。
sudo gem install rcov
で rcov をインストールします。
rcov --version
で rcov のバージョンが表示されればインストール成功です。
以下のコマンドを 10best のアプリケーションルートディレクトリで実行します。
rcov -x /var/lib/gems --rails test/**/*_test.rb
rcov をそのまま実行すると、実行したファイルすべてのカバレッジが表示されてしまいます。つまり、rails など gem のライブラリに関わるファイルのカバレッジも表示されてしまうので、-x /var/lib/gems というオプションを指定し、/var/lib/gems 以下のファイルの結果は表示しないようにしています。
また、--rails を指定すると、config や environment、vendor フォルダ以下のファイルも無視します。
こうして測定したカバレッジの結果は、html ファイルとして、アプリケーションルートディレクトリ以下に作成された coverage フォルダ以下に生成されます。
coverage/index.html をブラウザで開くと、以下の通り、グラフで各ソースコードのテストカバー率を見ることができます(緑がテストされた部分)。
個別のソースコードのカバレッジ情報は以下の通り。ソースコードの赤くハイライトされている部分が一度も実行されていない部分にあたります。
この赤い部分をできるだけなくすことを目指して、テストコードを追加していけば、アプリケーション内でテストされていない部分をなくし、品質を上げていくことができるわけです。
まとめ
テストとカバレッジツールをとりあげました。テストコードを書き、カバレッジツールを使いながらアプリケーション全体をカバーすることを目指すことによって、効率よくテストをおこなう方法(のほんの触りですが)を紹介しました。
ここまでのソースコードをアップロードしておきます。
» 10best - Google Code
svn checkout http://10best.googlecode.com/svn/trunk/ 10best
でチェックアウトするか、ブラウザでもリポジトリを閲覧することができます。




最近のコメント