UnitTestから RSpec on Rails に書き換えてみた (モデル編)

次の仕事から RSpec を使ってみようかかと思い RSpec on Railsを使ってみました。まずは、勉強にと UnitTest を RSpec on Rails に書き換えてみた。


ドキュメント・参考資料

早く、(日本の)書籍がでると良いですね ^^)

インストール

gem でインストール

sudo gem install rspec-rails

rspec 1.1.11, rspec-rails 1.1.11 がインストールされる

なぜか、実行時にエラーが発生。 hoe というモジュールが必要らしい

sudo gem install hoe

Spec を書く

1. まず、rspec用ディレクトリィーを作成。
cd PROJECT
script/generate rspec
2. 既にモデルはあるが rspec_model で モデル用のspecを作成
% script/generate rspec_model todo
      exists  app/models/
      create  spec/models/
      create  spec/fixtures/
:verwrite app/models/todo.rb? (enter "h" for help) [Ynaqdh] n
        skip  app/models/todo.rb
      create  spec/models/todo_spec.rb
      create  spec/fixtures/todos.yml
      exists  db/migrate
Another migration is already named create_todos: db/migrate/20080816083025_create_todos.rb

できた todo_spec.rb

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Todo do
  before(:each) do
    @valid_attributes = {
    }
  end

  it "should create a new instance given valid attributes" do
    Todo.create!(@valid_attributes)
  end
end
3. fixtues を test/fixtues からコピー
4. UnitTest(todo_test.rb) から Spec(todo_spec.rb) を作成
# todo_test.rb
require 'test_helper'

class TodoTest < ActiveSupport::TestCase
  def test_user_relation
    assert_equal("山田太郎", todos(:task1).user.name,
                 "todos.user_id の関連する users.name を取得")
  end

  def test_validates_presence_of
    todo = Todo.new(:due => '2008-09-01', :task=> '打ち上げ', :user_id => users(:yamada))
    assert(todo.save, "正常のデータの書き込み")
    todo.task = ''
    assert(!todo.save, "taskが空のデータの書き込み")
  end
end
# test_spec.rb
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Todo, "の取得は" do
  fixtures :todos, :users

  it "関連したUserの名前が参照できる" do
    Todo.find(todos(:task1).id).user.name.should == "山田太郎"
  end
end

describe Todo, "の保存は" do
  before(:each) do
    @todo = Todo.new(:due => '2008-09-01', :task=> '打ち上げ', :user_id => 1)
  end

  it "taskが空でなければ保存できる" do
    @todo.save.should be_true
    @todo.errors.should be_empty
  end

  it "taskが空なら保存できない" do
    @todo.task = ''
    @todo.save.should_not be_true
    @todo.should have(1).error_on(:task)
  end
end
5. 実行 rake spec:models または script/spec spec/models で実行できます。

script/specでは色々なオプションが設定できます。rake の場合はオプションを spec/spec.opts ファイルに指定します。

% script/spec -c -fs spec/models 

Todo の取得は
- 関連したUserの名前が参照できる

Todo の保存は
- taskが空でなければ保存できる
- taskが空なら保存できない

User の作成は
- 全ての項目が空でなければ保存できる
- nameが空が空なら保存できない
- emailが空が空なら保存できない
- passwordが空が空なら保存できない

User のパスワード作成・変更は
- 新規登録ではパスワードは暗号化される
- パスワードを変更しないとき、パスワードは再暗号化されない
- パスワードを変更したとき、パスワードは暗号化される

User の認証は
- 正しいemail,passwordで認証される
- 正しいくないpasswordで認証されない
- 正しいくないemailで認証されない

Finished in 0.39696 seconds

6. 実行の高速化

script/specの実行は、Railsライブラリー等の読み込み等で、そこそこ時間がかかります。そこで、Railsライブラリー等の読み込まれているRSpec用のサーバーを立ち上げてコマンドには --drb または -X オプションを付けて実行すると高速に実行されます。 dRubyスゴイ!!

%  script/spec_server &
% script/spec -c -fs --drb spec/models 

しかし、私の環境ではSpecが2回実行されてしまいます。

感想

  • RSpecのイディオム(.should ...) にまだ慣れてないが、なれればUnitTest同様に書ける
  • UnitTestに比べるとテストが簡潔に書ける気がする
  • specdocの良い書き方を習得する必要がある 日本語の良いサンプルが欲しい !! TODO: Rails勉強会@東京で聞いてみよう


次回は、コントローラ編





もう一つ書いたので載せておきます。RSpec ベテランの方 つっこみ 希望 :-)

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe User, "の作成は" do
  before(:each) do
    @user = User.new( :name => '海田太郎', 
                      :email => 'umida@rails.com',
                      :password => 'password1')
  end

  it "全ての項目が空でなければ保存できる" do
    @user.save.should be_true
    @user.errors.should be_empty
  end

  it "nameが空が空なら保存できない" do
    @user.name = ''
    @user.save.should_not be_true
    @user.should have(1).error_on(:name)
  end

  it "emailが空が空なら保存できない" do
    @user.email = ''
    @user.save.should_not be_true
    @user.should have(1).error_on(:email)
  end

  it "passwordが空が空なら保存できない" do
    @user.password = ''
    @user.save.should_not be_true
    @user.should have(1).error_on(:password)
  end
end

describe User, "のパスワード作成・変更は" do
  before(:each) do
    @user = User.new( :name => '海田太郎', 
                      :email => 'umida@rails.com',
                      :password => 'password1')
  end

  it "新規登録ではパスワードは暗号化される" do
    @user.save.should be_true
    user1 = User.find(@user.id)
    user1.password.should == Digest::SHA1.hexdigest('password1')
  end

  it "パスワードを変更しないとき、パスワードは再暗号化されない" do
    @user.save.should be_true
    user1 = User.find(@user.id)
    user1.save.should be_true
    user2 = User.find(user1.id)
    user1.password.should == user2.password
  end

  it "パスワードを変更したとき、パスワードは暗号化される" do
    @user.save.should be_true
    @user.save.should be_true
    user1 = User.find(@user.id)
    user1.password = 'password2';
    user1.save.should be_true
    user2 = User.find(user1.id)
    user1.password.should == Digest::SHA1.hexdigest('password2')
  end
end

describe User, "の認証は" do
  before(:each) do
    User.new( :name => '海田太郎', 
              :email => 'umida@rails.com',
              :password => 'password1').save!
  end

  it "正しいemail,passwordで認証される" do
    User.new(:email => 'umida@rails.com', 
             :password => 'password1').regitered?.should be_true
  end

  it "正しいくないpasswordで認証されない" do
    User.new(:email => 'umida@rails.com', 
             :password => 'password2').regitered?.should_not be_true
  end

  it "正しいくないemailで認証されない" do
    User.new(:email => 'umidane@rails.com', 
             :password => 'password1').regitered?.should_not be_true
  end
end