テストの共通ロジックをわかりやすく呼び出す手法

巷ではOne assertion per testなどと言われてますが、流石にアサーションを1回にするのは難しいところ。ならば共通部分を抜き出してテストを分離し、なるべくアサーションを減らす方向を取るのが現実的だと考えられます。
その際、なるべくロジックを共通化しようとするとどうしても引数が多くなってしまいますので、その実行部の可読性が下がってしまうのは避けられません。しかしHashを利用して引数をまとめるのは面倒だし、なるべくテストの記述に手間はかけたくないところ。
そんなときに、共通ロジックにまったく手を入れずにできる解決策があります。それは以前紹介した「メソッドの引数をわかりやすくする手法?」です。

実践(StarFrameのテストコードより)

http://coderepos.org/share/browser/lang/ruby/starframe/test/starframe/sprite/test_collidable.rb?rev=27523では、当たり判定モジュールが正しく動いているかテストをしています。
当たり判定のテストでは、「当たっている/当たっていない」「判定のみ/判定した上で衝突処理」という2軸の動作があるので、テストは4種類書く必要があります。

このテストでは、共通ロジックが以下のように定義されています。

  def assert_collide_sprite_and_sprite expected, collide = false, &block
    # 中略。共通のアサーション定義。
  end
http://coderepos.org/share/browser/lang/ruby/starframe/test/starframe/sprite/test_collidable.rb?rev=27523

そして、実際のテストでは以下のように呼び出されています。

  def test_collide_true
    assert_collide_sprite_and_sprite expected = true, collide = true do |sprite1, sprite2|
      sprite1.collide(:test, sprite2, :test)
    end
  end
  def test_collide_false
    assert_collide_sprite_and_sprite expected = false, collide = false do |sprite1, sprite2|
      sprite1.collide(:test, sprite2, :test)
    end
  end
  def test_collide_q_true
    assert_collide_sprite_and_sprite expected = true, collide = false do |sprite1, sprite2|
      sprite1.collide?(:test, sprite2, :test)
    end
  end
  def test_collide_q_false
    assert_collide_sprite_and_sprite expected = false, collide = false do |sprite1, sprite2|
      sprite1.collide?(:test, sprite2, :test)
    end
  end
http://coderepos.org/share/browser/lang/ruby/starframe/test/starframe/sprite/test_collidable.rb?rev=27523

2つの真偽値で共通ロジックを分岐しているんですが、その引数が単なるtrue/falseだけだと意味がわからなくなってしまうので、引数の中でダミー変数に値の代入を行うことで可読性を上げています。

  • expectedは結果の期待値。つまり当たり判定をチェックした際に戻る結果です。衝突していれば真、していなければ偽になるので、それに合わせて事前条件を自動的に設定します。
  • collideは衝突処理を行うかどうか。それに合わせて事後条件を自動的に設定します。

このように、一手間とも言えない程度のちょっとしたことで可読性を上げることができます。
一つ問題があり、変数名と引数の意味を間違えても何のエラーにもなりません。共通ロジックの引数を変更したのにこちらを変更し忘れると罠にしかなりませんのでご注意を。