LoginSignup
0
0

More than 5 years have passed since last update.

見易い定数同値テストのためのRSpec matcher その2

Posted at

前回の投稿で、定数の同値テストの結果を見易くするmatcherを作成した。
今回も類似のネタだが、もう少し細かく制御できるmatcherを作る方法を取り上げる。

概要

  • モジュールで定義した定数の同値テストをするためのmatcherを生成するメソッドを実装する。
  • 生成したmatcherは、前回紹介したmatcherと比較して以下の特徴を持つ。
    • モジュール内の特定の定数グループのみを対象とした比較ができる。
    • matcherを生成するので、テストケースを書く際にはモジュール名は書く必要がない。

解決したい問題

以下のようなクラスのrequestの結果をテストしたいケースを考える。

class Widget
  RES_SUCCESS = 0
  RES_SUSPEND = 1
  RES_ERROR   = -1

  STATUS_OK = 0
  STATUS_NG = -1

  def request
    # RES_XXXXを返す処理
  end
end

素直にテストを書くと記述すると以下のようになる。

wid = Widget.new
# requestがRES_ERRORを返す場合
expect(wid.request).to eq Widget::RES_SUCCESS
# => expected: 0
# =>      got: -1

しかし、これでは失敗したときのメッセージが分かりにくい。
我々は0とか-1とかではなく、RES_SUCCESSRES_ERRORという表記で読みたいのだ。

シンプルなケースでは、前回const_eqでも良い。
しかし、同じ値を持つ定数がクラス内に複数定義されていた場合(今回の場合はRES_SUCCESSSTATUS_OKが同じ値になる)に上手くいかないケースがある。

解決方法

matcherを生成するメソッドを定義

このようなクラスやモジュールの個別の問題に対処するためのmatcherを生成するメソッドを実装する。

def define_const_matcher(mozule, matcher_symbol, &filter)
  RSpec::Matchers.define matcher_symbol do |exp_symbol|
    match do |act_val|
      mozule.const_get(exp_symbol) == act_val
    end
    failure_message do |act_val|
      act_symbol = mozule.constants(true).
        select { |c| filter.call(c, mozule.const_get(c)) }.
        find { |c| mozule.const_get(c) == act_val }
      act_msg = if act_symbol.nil? then "     got: invalid value(#{act_val})"
                else "     got: #{mozule.name}::#{act_symbol}(#{act_val})"
                end
      exp_val = mozule.const_get(exp_symbol)
      "expected: #{mozule.name}::#{exp_symbol}(#{exp_val})\n" << act_msg
    end
  end
end

matcherを生成

今回の場合はWidgetクラスの定数の中でも、'RES'から始まる一群の定数を対象にテストできるmatcherが欲しい。
そのmatcherを生成するには次のようにdefine_const_matcherメソッドを呼び出してやればよい。

define_const_matcher(Widget, :widget_res_eq) { |c, _| c.to_s.start_with?('RES') }

使用例

生成したmatcherを先程の例に適用すると読み易いエラーを出力するテストケースを作ることができる。

wid = Widget.new
# requestがRES_ERRORを返す場合
expect(wid.request).to widget_res_eq :RES_SUCCESS
# => expected: Widget::RES_SUCCESS
# =>      got: Widget::RES_ERROR

補足

  • 今回は定数名で対象を絞り込んだが、define_const_matcherに渡すブロックを使い、値で絞り込むこともできる。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0