機能試験

controller な試験を書いてみる事に。
参考にしたのは http://jp.rubyist.net/magazine/?0013-RubyOnRails#l15
とりあえず、controller を作成しないとナニ。

$ ./script/generate controller task
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/task
      exists  test/functional/
      create  app/controllers/task_controller.rb
      create  test/functional/task_controller_test.rb
      create  app/helpers/task_helper.rb
$ ./script/generate controller ajax
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/ajax
      exists  test/functional/
      create  app/controllers/ajax_controller.rb
      create  test/functional/ajax_controller_test.rb
      create  app/helpers/ajax_helper.rb
$

試験を書くのですが、機能試験的には

  • ブラウザから渡されるパラメータ
  • サーバが返却するレスポンスやデータ

の確認を実施との事。

確認可能なレスポンスのナニは

  • HTTP レスポンス
  • レンダリングしたテンプレートの名前
  • view に渡された変数の値
  • session、flash
  • テンプレートを元に出力された HTML タグの値

等が確認可能との事。

とりあえず、簡単なのから検討してみると task コントローラの index アクションは List を全件ナニして @lists に渡している。render するのは task/index ?
気になるのは index.rhtml から何段かに渡って render してるんですが、これはどうなるんだろうか。コードとしては以下のような感じッスか?? (とりあえず、インスタンス変数のチェキは別途)

def test_index
  get :index
  assert_response :success
  assert_template 'index'
end

動かしてみたが、当然の如く index というアクションが無い、との返答。task_controller.rb を実装してみます。

と、

  • find_all に文句を言われる
  • default.rhtml が無い、と文句を言われる

げ。当たり前だな。default.rhtml はコピっておこう。で実行すると今度は index.rhtml がねぇと。で、再度実行すると、render してる _list.rhtml も要求されているらしい。touch で空なナニを作成するとどうなるかな。

$ touch app/views/task/_list.rhtml
$ touch app/views/task/_item.rhtml
$ ls app/views/task
_item.rhtml  _list.rhtml  index.rhtml
$ ls app/views/layout
default.rhtml
$ ruby test/functional/task_controller_test.rb 
Loaded suite test/functional/task_controller_test
Started
..
Finished in 0.085107 seconds.

2 tests, 3 assertions, 0 failures, 0 errors
$

を、通った。ファイルがあれば良いのか??とゆー事で default.rhtml と index.rhtml を両方空にして再度実行してみる。

$ rm app/views/layout/default.rhtml
$ rm app/views/task/index.rhtml
$ touch app/views/layout/default.rhtml
$ touch app/views/task/index.rhtml
$ ruby test/functional/task_controller_test.rb 
Loaded suite test/functional/task_controller_test
Started
..
Finished in 0.039895 seconds.

2 tests, 3 assertions, 0 failures, 0 errors
$

げ、通った。てか、layout/default.rhtml と task/index.rhtml だけで良さげ。

$ rm app/views/task/_*.rhtml
$ ruby test/functional/task_controller_test.rb 
Loaded suite test/functional/task_controller_test
Started
..
Finished in 0.040804 seconds.

2 tests, 3 assertions, 0 failures, 0 errors
$

つーか、index アクションは index しかレンダーしてないとゆー認識で良いのかなぁ。その先って試験の要素としては見てなさげではある。できあがり状態で試してみる必要はあるかも。
とりあえず、view なナニのサイズが 0 で良いとゆー事は製造の順番としては model → controller → view ってコトになるのかなぁ。しかも view は目視? ただ、test ディレクトリの mocks だの integration だのとゆーディレクトリも気になる。

で、変数見るために assert を追加

  def test_index
    get :index
    assert_response :success
    assert_template 'index'
    assert_equal lists(:first).id, assigns(:lists)[0].id
    assert_equal lists(:another).id, assigns(:lists)[1].id
  end

とりあえず試験としては通ってるんですが、lists のコレクションを map して assigns(:list) の map なナニと assert_equal ってワザは使えんのかなぁ。(lists のコレクションって言い方微妙スギ)

この調子で ajax なコントローラの試験も検討してみる。以下にアクション列挙。メソドは基本的に POST のはず。

  • add_list
    • パラメータとして :title が渡される
    • save に成功したら task/list がテンプレートでステータスは 200
    • テンプレートに渡されるのは list
    • save に失敗したらどうなるんだろ (テンプレートは同じ??)。ステータスは 406
  • destroy_list
    • パラメータとして :id が渡される
    • :id なナニは destroy される
    • 存在しない :id だと??
    • テンプレートはなし
  • add_item
    • パラメータとして :id と :note が渡される
    • save は共通なんで成功なパターンのみ試験で良いはず
  • completion_item
    • パラメータとして :id が渡される
    • completion 列が toggle! される
    • テンプレートなし (:nothing??)
  • edit_item
    • パラメータとして :id と :value が渡される
    • :value が空の場合、Item.fine(:id) な行が destroy される
    • :value が空でない場合、note 列が更新
    • テンプレートは指定なし。(item??)
  • update_positions
    • パラメータとして :sortable_list が渡される。これは id の配列。
    • 渡された id の配列順に position が更新
    • テンプレートなし

とりあえず、上記を元に試験を add_list を書いてみて実行してみる。

  def test_add_list
    post :add_list, :title => 'adding'
    assert_template 'task/_list'
    assert_response :success
  end

テンプレートの名前はファイル名じゃないと駄目らしい。一応試験は通ったぽい。

$ ruby test/functional/ajax_controller_test.rb
Loaded suite test/functional/ajax_controller_test
Started
..
Finished in 0.069392 seconds.

2 tests, 3 assertions, 0 failures, 0 errors
$

レコード追加の確認したいんですが、どうすりゃええんでしょうか。こんな感じ??

assert_equal 'adding', List.find(3).title

ええと、違うな。レコード追加は model の仕事だからこっちじゃ確認しなくて良い? ま、いいや。とりあえずそゆ事にしておいて、request/responce のナニだけを確認しましょう。
でも、render_add とか render_destroy の試験はデキるか。て、protected なんで無理? これもスルーしておこう。なんかダメダメだなぁ。

で、一応全部の試験を実装してみたんですが、render も :text とかって assert_template で確認できないみたい。(本当か??)

追記

test_add_list ん中で、 p List.find(:all) してみたら追加されたレコードの id が 32 とかになっているのを発見。仕方がないので以下のような assert を追加。

    assert_equal 3,  List.find(:all).entries.length

追記 2

test ディレクトリの中に integration というナニがある。ググッてみた所、rails 1.1 で新たに導入されたものらしい。

追記 3

とりあえず、書いた試験を以下に。

test/functional/ajax_controller_test.rb

require File.dirname(__FILE__) + '/../test_helper'
require 'ajax_controller'

# Re-raise errors caught by the controller.
class AjaxController; def rescue_action(e) raise e end; end

class AjaxControllerTest < Test::Unit::TestCase
  fixtures :lists, :items

  def setup
    @controller = AjaxController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  # Replace this with your real tests.
  def test_truth
    assert true
  end

  def test_add_list
    post :add_list, :title => 'adding'
    assert_template 'task/_list'
    assert_response :success

    assert_equal 3,  List.find(:all).entries.length
  end

  def test_destroy_list
    post :destroy_list, :id => 1
    assert_response :success

    assert_raise(ActiveRecord::RecordNotFound) {List.find(1)}
  end

  def test_add_item
    post :add_item, :id => 1, :note => 'zzzzzz'
    assert_response :success
    assert_template 'task/_item'

    assert_equal 'zzzzzz', List.find(1).items.last.note
  end

  def test_completion_item
    assert_equal items(:first).completion, Item.find(1).completion
    post :completion_item, :id => 1
    assert_equal items(:first).completion, !Item.find(1).completion
  end

  def test_edit_item
    post :edit_item, :id => 1, :value => 'xxxxx'
    assert_equal 'xxxxx', Item.find(1).note

    post :edit_item, :id => 1, :value => ""
    assert_raise(ActiveRecord::RecordNotFound) {Item.find(1)}
  end

  def test_update_positions
    post :update_positions, :sortable_list => [3, 2, 1]
    assert_equal 0, Item.find(3).position
    assert_equal 1, Item.find(2).position
    assert_equal 2, Item.find(1).position
  end
end