Rails で文献管理システムを作ってみるよ(2/n)

前回はこちら.

  • 本日のお品書き
    • test の作成, 修正, 追加
    • Article 作成時の修正
      • (start|end)_page は integer で渡す. model でそれをチェックする.
      • year は Date オブジェクトを渡す. 必要なのは year のみ.

の予定でしたが,方針を変えて, 勉強がてら RSpec on Rails を使用してみます.前回, 既に scaffold を使って model, view, controller を作成しているのですが, これを一度忘れてみましょう(なんてこった).

 $ ruby script destroy scaffold Article

さよ〜なら〜

RSpec on Rails の導入

ついでに, Selenium on Rails も導入しておきます.

 $ rake db:create
 $ ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec
 $ ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails
 $ ruby script/plugin install http://svn.coderepos.org/share/lang/ruby/selenium_rc_spec

RSpec の準備

 $ ruby script/generate rspec

Article モデルの作成

まずは rspec_model を作成します.

 $ ruby script/generate rspec_model Article \
   title:string \
   authors:string \
   year:datetime \
   journal:string \
   volume:integer \
   start_page:integer \
   end_page:integer
   ...
 $ rake db:migrate
 $ rake db:text:clone   # Spec 用データベースを生成

というわけで, 以後は

(1) Spec で振る舞いを記述
(1) テスト(赤)
(1) コードの実装
(1) テスト(緑)
(1) リファクタリング

の順に開発を行なっていきます.

振る舞いの記述

spec/model/article_spec.rb で Spec を記述します. 何も記述していないと,

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

describe Article do
  before(:each) do
    @article = Article.new
  end

  it "should be valid" do
    @article.should be_valid
  end
end

となっていると思いますので, これを修正します.
例えば「タイトルが空の場合は Article を記述できない」という Spec を記述するならば

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

describe Article," を生成するとき" do
  before(:each) do
    @title = 'タイトルの例'
    @authors = '著者の例'
    @year = '2004' # 年の例
    @journal = '雑誌の例'
    @volume = '雑誌の巻数の例'
    @start_page = '001' # start page の例
    @end_page = '020' # end page の例
  end

  it "は, title が空の場合 valid ではないこと" do
    @article = Article.new(
      :title => nil,
      :authors => @authors,
      :year=> @year,
      :journal => @journal,
      :volume => @volume,
      :start_page => @start_page,
      :end_page => @end_page)
    @article.should_not be_valid
  end
end

といった感じですかね. ちなみに, ファイルは UTF-8 で作成しておきます. あと, config/environment.rb の冒頭に $KCODE='u' を追加しておきます.

テスト(赤)

ここで spec でテストを行なうと,

 $ rake spec
  ...
  1)
  'Article を生成するとき は, title が空の場合 valid ではないこと' FAILED
  expected valid? to return false, got true
  ./spec/models/article_spec.rb:23:

  Finished in 0.339274 seconds

  1 example, 1 failure
  rake aborted!

となって, app/model で実装していないので, テストは失敗します.

コードの実装

次に app/model/article.rb を実装します. 以下の様に.

class Article < ActiveRecord::Base

  validates_presence_of :title
end
テスト(緑)

そして spec を実行すると

 $ rake spec
   ...
   .

   Finished in 0.339777 seconds

   1 example, 0 failures

となります. spec に記述した振舞い通りに model が動いていることが test された訳です.

最終的に

Article モデルはどうしたかったのか, と言えば,

  • title, authors, year は必須.
  • start_page と end_page は, start_page <= end_page とする.

でした.

そんな訳で, 書いた spec とモデルは以下の通り

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

describe Article," を生成するとき" do
  before(:each) do
    @title = 'タイトルの例'
    @authors = '著者の例'
    @year = Date.today
    @journal = '雑誌の例'
    @volume = '雑誌の巻数の例'
    @start_page = '001' # start page の例
    @end_page = '020' # end page の例
  end

  it "は, title が空の場合 valid ではないこと" do
    @article = Article.new(
      :title => nil,
      :authors => @authors,
      :year=> @year,
      :journal => @journal,
      :volume => @volume,
      :start_page => @start_page,
      :end_page => @end_page)
    @article.should_not be_valid
  end

  it "は, authors が空の場合 valid ではないこと" do
    @article = Article.new(
      :title => @title,
      :authors => nil,
      :year=> @year,
      :journal => @journal,
      :volume => @volume,
      :start_page => @start_page,
      :end_page => @end_page)
    @article.should_not be_valid
  end

  it "は, year が空の場合 valid ではないこと" do
    @article = Article.new(
      :title => @title,
      :authors => @authors,
      :year=> nil,
      :journal => @journal,
      :volume => @volume,
      :start_page => @start_page,
      :end_page => @end_page)
    @article.should_not be_valid
  end

  it "は, start_page < end_page でなければならないこと" do
    @article = Article.new(
      :title => @title,
      :authors => @authors,
      :year=> @year,
      :journal => @journal,
      :volume => @volume,
      :start_page => @end_page,
      :end_page => @start_page)
    @article.should_not be_valid
  end

end

で, これをテスト(緑)にしたモデルが

class Article < ActiveRecord::Base
  require 'date'

  validates_presence_of :title, :authors, :year

  protected
  def validate
    if start_page > end_page
      errors.add :start_page, "始めのページが最後のページより大きいよ?"
    end
  end
end

次は

  • fixtures を使って, spec をもっと簡潔に書く
  • controller, view も spec を使うよ

へ続きます. 多分.