unit test

parabola の feed.rb の unit test を書き終えた。最後に setting.rb の test の記述に着手。
以下に気づきをメモ

  • Hash#clear なんてメソドは Ruby のリファレンスにも出てなかったのだけれど、多分見落してるんだろうな。
  • Configuration については、settings テーブルに最初からデータがあるの前提?? (こんなコマカい事を気にしてるから作業が進捗しない、という微妙な指摘はスルー)
  • 上記はダウト。詳細は追記を。
  • Configuration と ConfigManager は ruby を理解してないと分かりにくい。追記でトレース
  • 試験項目を以下に
    • テーブルにデータが存在しない状態で以下のキーでアクセスした場合、デフォルトの値が返却される事。
      • :admin_feeds_per_page
      • :articles_per_page
      • :list_by_feed
      • :lock_account_creation
      • :show_tags_size
      • :title
    • 上記以外のキーで hash なアクセスがあった場合、nil が返却される事。
    • テーブルにデータが存在する場合には設定されている値が返却される事。
    • テーブル上の値を変更し、reload した後にインスタンス変数に値が反映されている事。
    • テーブルにデータが存在しない状態で config.is_ok? したら nil 返却 (しないように見えるんですが)
      • :title だけ設定、だと常に真で、:title だけ設定してないと常に偽な書き方に見えるんですが気のせいですか??
      • ruby って bool な式を縦に連続して書いたら、それらの & が返却、とか??
    • Configuration.fields へのアクセス
    • settings へのアクセス
    • reload 時、正常に normalize されているか
    • config メソドを使用した上記の動作確認

追記 (Configuration と ConfigManager のトレイス)

まず、以下にソースを

class Configuration < ConfigManager
  setting :admin_feeds_per_page, :integer, 40
  setting :articles_per_page, :integer, 20
  setting :list_by_feed, :bool, false
  setting :lock_account_creation, :bool, false
  setting :show_tags_size, :bool, false
  setting :title, :string, 'Online RSS Aggregator'
end

def config
  $config ||= Configuration.new
end

クラス定義の中で式が実行されている。これはクラス定義時 (クラス定義を ruby が読み込んだ時、と言えば良い??) に実行というか評価される (はず)。ここで呼び出されている setting は ConfigManager クラスにおいて定義されている。以下に ConfigManager の定義を。

class ConfigManager
  def initialize
    reload
  end

  def is_ok?
    settings.include?("admin_feeds_per_page")
    settings.include?("articles_per_page")
    settings.include?("lock_account_creation")
    settings.include?("list_by_feed")
    settings.include?("show_tags_size")
    settings.include?("title")
  end

  def reload
    settings.clear
    Setting.find(:all).each do |line|
      settings[line.name.to_s] = normalize_value(line)
    end
  end
    
  def [](key)
    value = settings[key.to_s]
    (value.nil?) ? Configuration.fields[key.to_s].default : value rescue nil 
  end  

  def self.fields 
    @fields ||= {}
  end

  protected

  class Item < Struct.new(:name, :ruby_type, :default)
  end
  
  def normalize_value(line)
    case (Configuration.fields[line.name.to_s].ruby_type rescue :string)
    when :bool
      ( line.value.to_i != 0 ) ? true : false
    when :int
      line.value.to_i
    else
      line.value
    end
  end
    
  def self.setting(name, type, default)
    item = Configuration::Item.new 
    item.name, item.ruby_type, item.default = name, type, default
    fields[item.name.to_s] = item
  end

  def settings
    @hash ||= {}
  end
end

ConfigManager#setting は protected で定義されているので子クラスからの呼び出しも可能。ConfigManager.setting では :name、:ruby_type、:default という属性を持つ構造体 Item のインスタンスを生成し、参照を item に代入。それぞれの属性に引数の値を設定し、ConfigManager のインスタンス変数 @fields に設定。ちなみに @fields は Hash で fields という public なメソドにてアクセス可能。

という事でクラス定義時に @fields はスデに用意されている。で、グローバル変数 $config を参照するためのメソド config を定義して configuration.rb は終了。

では次に、config メソドが呼び出された場合、どうなるのか、という事ですが最初のアクセス ($config が nil の時) において、Configuration.new が呼び出されますが、Configuration クラス定義中にはメソドが定義されておらず、呼び出されるのは親クラス (ConfigManager) の initialize となるはずです (根拠ナシ、別途調査)。

ConfigManager#initialize では ConfigManager#reload が呼び出されており、ここでは ConfigManager#settings (これもインスタンス変数 @hash ヘのアクセサ) を使って settings テーブルから読み出した値を正規化したものを @hash に格納している。基本的にテーブルに格納される name 列の値は 6 つに限定されている (Configuration クラスの定義を変更すれば拡張は可能)。

で、[] というメソドも定義されており (fields への格納メソドも公開)、reader についてはサービス満点なんですが、settings テーブルへの出力やら @hash への値の格納メソドが無いんですが、おそらくは controller が直接テーブルに出力した後に、ConfigManager#reload を呼び出すんでしょうな。

これだけナニなクラス定義ができるんだったら、Feed とかも少しエレガントに記述して欲しかったよな、と思ってしまったのだけれど、それは今後のタスクとなっている模様。
さ、試験を書こう。

追記 2

その後の気づきを以下に。

  • テーブルから fixture なナニを destroy してもオブジェクトとしては残っている。(Test::Unit::TestCase.use_instantiated_fixtures = true で @ふんちゃらで参照可能にした場合のみ??)
  • 微妙なバグあり。デフォルトな値の確認試験が通らん、と思っていたら記述の不整合あり。クラス定義では setting に :integer を渡しているのに ConfigManager#normalize_value の case では :int になっておる。
  • $config をいちいち初期化しないといけんかった。test メソド毎にDB リロードするのは良いのですがグローバル変数は初期化されないのね、みたいなー。

追記 3 (is_ok? について)

微妙、と書いたがやはり NG みたい。以下のコードでは "title" のみが include されていれば OK な挙動のはず、と思ったがやっぱそうでした。

  def is_ok?
    settings.include?("admin_feeds_per_page")
    settings.include?("articles_per_page")
    settings.include?("lock_account_creation")
    settings.include?("list_by_feed")
    settings.include?("show_tags_size")
    settings.include?("title")
  end

で、以下のように修正したが良いのだろうか。

  def is_ok?
    settings.include?("admin_feeds_per_page") &&
    settings.include?("articles_per_page") &&
    settings.include?("lock_account_creation") &&
    settings.include?("list_by_feed") &&
    settings.include?("show_tags_size") &&
    settings.include?("title")
  end