テストを書いてからコードを実装してみる
これまでテストした中で、いくつかの動作は、現状のままでは問題があることも分かってきた。その一つは、伝票の明細行を削除するところ。調子よく削除していくと、最後の1件も削除できてしまう。そして、伝票には明細行が無い状態になり、挿入する方法も無いことに気付き、途方に暮れる...。(挿入・削除・コピーのリンクが、明細行の中にあるので。)
この問題に対して暫定的に、行削除の結果、明細行が0件の場合は、自動的に1行追加するようにして回避することにしてみた。そして今回は、コードを修正する前に、目指す状態のテストを書いてみた。
まずテスト
# テスト: test/functional/journals_controller_test.rb require File.dirname(__FILE__) + '/../test_helper' class JournalsControllerTest < ActionController::TestCase # 2行以上の明細状態から削除 def test_should_delete_row xml_http_request :post, :delete, {:index =>'j123456789', :journal=>{"j123456789"=>{:comment=>"test", :yen=>"1000", :index=>"j123456789", :position=>1}, "j123456790"=>{:comment=>"test", :yen=>"1000", :index=>"j123456790", :position=>2} } } assert_response :success assert_select_rjs :remove, "j123456789" assert_select_rjs "journals_footer" do assert_select "tbody>tr" do assert_select "th", 3 assert_select "th[align=right][colspan=2]:nth-child(2)", 1 assert_select "th input[value=1,000]", 1 end end end # 最後の1行を削除 def test_should_delete_last_row xml_http_request :post, :delete, {:index =>'j123456789', :journal=>{"j123456789"=>{:comment=>"test", :yen=>"1000", :index=>"j123456789", :position=>1} } } assert_response :success assert_select_rjs :remove, "j123456789" assert_select_rjs :insert, :before, "journals_footer" do ######32行目 assert_select "tbody>tr" do assert_select 'th', 2 assert_select 'td', 2 assert_select 'td input[value]', false end end assert_select_rjs :replace, "journals_footer" do assert_select "tbody>tr" do assert_select 'th', 3 assert_select "th[align=right][colspan=2]:nth-child(2)", 1 assert_select "th input[value=0]", 1 end end end end
- 当然のことながら、まだ実装していないので、このテストはFailure、失敗する。
1) Failure: test_should_delete_last_row(JournalsControllerTest) [/Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/assertions/selector_assertions.rb:467:in `assert_select_rjs' ./test/functional/journals_controller_test.rb:32:in `test_should_delete_last_row' /Library/Ruby/Gems/1.8/gems/activesupport-2.0.2/lib/active_support/testing/default.rb:7:in `run']: No RJS statement that replaces or inserts HTML content. 2 tests, 13 assertions, 1 failures, 0 errors
- /test/functional/journals_controller_test.rbの32行目で発生しているようだ。(test_should_delete_last_rowメソッドの以下の部分)
assert_select_rjs :insert, :before, "journals_footer" do ######32行目
コードの実装
- 自動的に1行追加する機能がないのが原因なので、早速、実装してみる。(オレンジ色の部分)
class JournalsController < ApplicationController
before_filter :for_editing_rows, :only=>[:insert, :copy, :delete]
...(中略)...
def delete
@effect_item = @slip.delete_journal(params[:index])
render :update do |page|
highlight_row(page, @effect_item.index, :duration=>2, :startcolor=>"'#666666'")
page.delay(1) do
page.remove params[:index]
numbering_row(page, @effect_item.position - 1)
if @slip.editing_journals.empty?
@effect_item = @slip.insert_journal
page.insert_html :before, 'journals_footer', render(:partial=>'journals/form', :object=>@effect_item)
highlight_row(page, @effect_item.index, :duration=>2)
numbering_row(page, @effect_item.position - 1)
end
page.replace 'journals_footer', :partial=>'journals/footer'
end
end
end
private
# 挿入、削除、コピーの前処理
def for_editing_rows
@slip = Slip.new #(params[:slip])
@slip.make_journals(params[:journal])
end
def render_insert(position, id)
render :update do |page|
page.insert_html position, id, render(:partial=>'journals/form', :object=>@effect_item)
highlight_row(page, @effect_item.index, :duration=>2)
numbering_row(page, @effect_item.position - 1)
page.replace 'journals_footer', :partial=>'journals/footer'
end
end
end
- すると、今度はErrorが発生。
- 引数の個数が間違っていると。(引数1のところ、0になっていると読むようだ。)
- 「@slip.insert_journal」の部分が問題箇所。(app/controllers/journals_controller.rb:117)
1) Error: test_should_delete_last_row(JournalsControllerTest): ArgumentError: wrong number of arguments (0 for 1) /Users/bebe/railsapp/test_slip202/app/controllers/journals_controller.rb:117:in `insert_journal' ...(中略)... 22 tests, 190 assertions, 0 failures, 1 errors
- 「def insert_journal(params_index = nil)」と修正したが、またしてもErrorが発生。
1) Error: test_should_delete_last_row(JournalsControllerTest): NoMethodError: You have a nil object when you didn't expect it! The error occurred while evaluating nil.position /Users/bebe/railsapp/test_slip202/app/models/slip.rb:74:in `insert_journal' ...(中略)... 22 tests, 190 assertions, 0 failures, 1 errors
- 上記2つのエラーで修正したのはオレンジ色の部分。以下のようになった。
# モデル: app/models/slip.rb ...(中略)... def insert_journal(params_index = nil) current_journal = journal_at(params_index) current_position = (current_journal.position rescue 1) ######74行目 insert_index = current_position - 1 insert_journal = Journal.new(:position=>current_position) @editing_journals.insert(insert_index, insert_journal) insert_journal end ...(中略)...
テストが成功
以上の修正で、テストは通り、結果はグリーンになった。そして、やはり最後はマウスクリックテストだ。最後の1行を削除してみると...削除した後、すぐに1行追加された!
現実
以上の経過は、経験不足の自分が思い描く空想のテスト駆動開発だ。上記のテスト、コード実装は実際にやってみたことだし、最終的なコードは上記の通りになっているが、現実の途中経過は、こんなにシンプルな手順では終わらなかった...。以下、自分のメモ。
- 先にテストを書くが、そのテストが正しいのかどうか不安。実装中に何度も書き直した。
- →テストに慣れれば、もう少し自信を持てるし、的確に書けるかもしれない。
- 上記に関連して、不慣れな現状では、テストは極力シンプルに書く。代入や繰り返し、条件分岐が多くなると、テストをテストしたい気分になってくる。
- →パラメーターに値を直接書いてしまっても、自分が分かり易ければ、それでもいいと割り切ってしまう。
- →おそらく条件分岐なんか必要ない。テストを分ければいい。
- →以下のようにループするよりも...
[:number, :executed_on, :total_yen, :base].each do |field| assert slip.errors.invalid?(field) end
-
- →assertを何度も書いた方が見やすい、直感的。
assert slip.errors.invalid?(:number) assert slip.errors.invalid?(:executed_on) assert slip.errors.invalid?(:total_yen) assert slip.errors.invalid?(:base)
- autotestはコード変更の差分をチェックして、必要な部分のテストだけ実施してくれるようだが、autotestが通っても、改めてすべてのテストを実行すると失敗していることがあった。
- →メソッドの割り振りミス?(JournalコントローラーからSlipモデルを呼び出しているから?)
- →常にすべてのテストを実施するように変更してしまいたい。(autotestの設定ファイルで出来るのだろうか?)
- モデルのテストも必要だったかもしれない...。コード実装中に新たなテストの必要性を感じたら、その都度追加した方が良さそう。
# テスト: test/unit/slip_test.rb require File.dirname(__FILE__) + '/../test_helper' class SlipTest < ActiveSupport::TestCase ...(中略)... def test_insert_journal_in_nil slip = Slip.new(:number=>"1", :executed_on=>"2/1", :total_yen=>"1000") slip.make_journals("j123456789"=>{:comment=>"test", :yen=>"1000", :position=>1, :index=>"j123456789"}) slip.delete_journal("j123456789") assert_difference "slip.editing_journals.size" do insert_journal = slip.insert_journal assert_nil insert_journal.comment assert_nil insert_journal.yen end end
もう少しテストの経験が必要だ...。とりあえず上記の方針で、続けてみる。