TDD

RubyでTest::Baseっぽいのを作る  [ruby]  [TDD]  [sample_code]

Rubyで動的に関数定義3
で書いた方法をつかって、PerlのTest::Baseっぽいのを書いてみる。

(Ruby Test::Baseってのがすでにあるみたいだけどね。)

まずは、test_base.rb


require 'test/unit' 
class TestBase < Test::Unit::TestCase
  @@test_template = {}
  def run(*args)
    return if@method_name.to_s == "default_test"
    super
  end
  def self.start_test
    self.parse if(@@test_template.empty?)
    self.generate_tests 
  end
  private
  def self.parse
    test_name = ""
    buf = ""
    buf_mode = ""
    while line = DATA.gets do 
      line.chomp!
      if(line =~ /^(===|---)/)
        if(buf != "" && test_name != "" && buf_mode != "")
          @@test_template[test_name][buf_mode] = eval(buf)
          buf = ""
        end
        if(line =~ /^=== (.*)$/)
          test_name = $1
          @@test_template[test_name] = {}
        elsif(line =~ /--- input (.*)$/)
          @@test_template[test_name][:method_name] = $1
          buf_mode = :input
        elsif(line = ~/--- expected/)
          buf_mode= :expected
        end
      else
        buf += line
      end
    end
    if(buf != "" && test_name != "" && buf_mode != "")
      @@test_template[test_name][buf_mode] = eval(buf)
      buf = ""
    end
  end
  def self.generate_tests
    @@test_template.each do |test_name,test_args|
      define_method(test_name) do
        result=__send__(test_args[:method_name],test_args[:input])
        assert_equal test_args[:expected], result, " error on #{test_name}"
      end
    end 
  end
end

DATAの処理のところが、ちょっと(かなり?)スパゲッティーだけど、generate_testsの所でテスト用の関数をDATA内容に合わせて作成してる。

Test::Unit::TestCaseのrunメソッドで”default_test”を無視するようにして、TestCaseがテスト未定義時に勝手にdefault_testを実行するのを防いでる。(この部分を抜くとどうなるか試せば言ってる意味がわかるはず)

実際のテストのほうは、こんな感じで書く。


require './test_base'
require './testee' 

class TestBaseTest < TestBase
  def setup #setupとかは通常のTestCaseと同じ
    @testee = Testee.new
  end
  def login_test(login_name) # DATAを利用するテストはtest_以外のメソッド名
    @testee.login(login_name)
    @testee.who_am_i?
  end
  def test_without_data # DATAを使わないテストはtest_で始まるメソッド名
    true
  end
end
TestBaseTest.start_test #最後にstart_testでテストを生成しておく
__END__ #ここからテストに渡すパラメータ
=== test_should_login_by_hoge
--- input login_test #inputの後ろに利用するメソッド名
:hoge
--- expected
:hoge
=== test_should_login_by_fuga
--- input login_test
:fuga
--- expected
:fuga

inputの後ろのメソッド名が一個しかかけなかったり、フィルタとか便利なものがなかったり、”ruby Test::Base”よりかなりはしょってるけど、1ファイルで出来てるのでその辺はしょうがないと割り切る。

じゃあの。

Railsテスト時に日付を固定にする方法 (for Test::Unit)  [ruby]  [rails]  [TDD]

Railsで試験をする時に、日付に依存する機能(次の誕生日までの日数を求めるとか。。)がある時、今までならfixtureに


date: <%= Date.today().to_s %>

とか、動的にfixtureを定義していたのですが、いろいろロジックが複雑になりだしたので、発想を逆にすることにしました。

つまり、テストの日付を固定するという方法です。

基本的に、Date.today()や、Time.now()を上書きしてやるのですが、他のテストに影響を与えないようにやる必要があります。
そこで、Test::Unit::TestCaseのsetup/teardown内で、関数の再定義、もとの定義への戻しをやってやります。

hoge_controller_test.rb


:
#このテストは2008年9月で試験
class Date
  def self.fixed_today
    return Date.new(2008,9,1)
  end
end

class HogeControllerTest < Test::Unit::TestCase 
  def setup
    Date.instance_eval do
      alias :orig_today :today
      alias :today :fixed_today
    end
  end
  def teardown
    Date.instance_eval do
      alias :fixed_today :today
      alias :today :orig_today
    end
  end
:
end

特異メソッドのaliasを作成するので、instance_evalを利用してaliasによる関数の入れ替えをしています。

Rubyで動的に関数定義3(for Test::Unit)  [ruby]  [tips]  [TDD]

Rubyで動的に関数定義
Rubyで動的に関数定義2
からの続き。

で、今回いろいろ調べていた大元の理由である「テストケースの自動生成」だけど、こんな感じで使えそう。


テスト対象


class Testee
  def login(login_user)
    @login_user ||=login_user
  end
  def who_am_i?
    @login_user
  end
end

一つのテストケース内でログインの試験を複数回やると、初回のログイン情報が残るためにfailする。


require 'test/unit'
require 'testee' 

class TesteeTest < Test::Unit::TestCase
  def test_should_login
    @testee.login(:hoge)
    assert_equal :hoge, @testee.who_am_i? # => ok
    @testee.login(:fuga)
    assert_equal :fuga, @testee.who_am_i? #=> fail :hogeが残ってる
  end
end



今までなら、複数の試験を手書きしてた。


そこで、今回勉強してきた、define_methodを利用してテストケースを自動生成してみる。


require 'test/unit'
require 'testee' 

class TesteeTest < Test::Unit::TestCase
  def self.generate_test
    while login_name = DATA.gets do
      login_name.chomp!
      define_method("test_should_login_by_#{login_name}") do
        @testee.login(login_name)
        assert_equal login_name, @testee.who_am_i? , " error on login by #{login_name}"
      end
    end 
  end
  def setup
    @testee = Testee.new
  end
end
__END__
:hoge
:fuga



うまくやれば、PerlのTest::Baseくらい使い勝手が良くなるかもね。

MVCアーキテクチャでのテストの分け方  [TDD]  [programming]  [idea]

こういう風に分けると意外とうまくいく気がする。

  • Security:主にController
  • Routing:主にController
  • Logic:主にController。
    (ActiveRecordアーキテクチャ(Railsとか)だとControllerだけど、他のフレームワークの場合ModelとControllerの間の層にもこのあたりの機能が入ると思われる。)
  • Server Side Validation:主にModel
  • Client Side Validatoin:主にjavascript
  • Server Side View:主にView。
    テンプレートによるView生成とか
  • Client Side View:主にjavscript。
    Formの入力制御とか、Viewフィルタとか、Tableのsortとか。Ajaxとか。



このブログではコメントとかが入らないので反応が見れないのが残念。(はてぶとかに載ればいいけどね。)