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