Rubyノート - Test::Unit は assertion の数をいかにしてカウントするか

Test::Unit を使ったテストを実行すると以下のような結果が出力される。

Loaded suite /home/h1mesuke/wwx/test/unit/ts_all
Started
...................
Finished in 1.229511 seconds.

19 tests, 1131 assertions, 0 failures, 0 errors

test の数は test_xxx なメソッドの数を調べればわかる。failure と error は例外をキャッチしてそれをカウントすればいい。では、果たして、assertion の数というのはいかにしてカウントされているのだろうか。興味があったので調べてみた。

まず、Test::Unit::Assertions から

# /usr/lib/ruby/1.8/test/unit/assertions.rb

    ##
    # Test::Unit::Assertions contains the standard Test::Unit assertions.
    # Assertions is included in Test::Unit::TestCase.
    #
    # To include it in your own code and use its functionality, you simply
    # need to rescue Test::Unit::AssertionFailedError. Additionally you may
    # override add_assertion to get notified whenever an assertion is made.
      private
      def _wrap_assertion
        @_assertion_wrapped ||= false
        unless (@_assertion_wrapped)
          @_assertion_wrapped = true
          begin
            add_assertion
            return yield
          ensure
            @_assertion_wrapped = false
          end
        else
          return yield
        end
      end

      ##
      # Called whenever an assertion is made.  Define this in classes that
      # include Test::Unit::Assertions to record assertion counts.

      private
      def add_assertion
      end

これが答え。すべての assertion は _wrap_assertion を使って自身の実装をラップしており、呼び出される度に add_assertion が一度だけ呼び出されるようになっている。こんな具合に↓

      ##
      # The assertion upon which all other assertions are based. Passes if the
      # block yields true.
      #
      # Example:
      #   assert_block "Couldn't do the thing" do
      #     do_the_thing
      #   end

      public
      def assert_block(message="assert_block failed.") # :yields: 
        _wrap_assertion do
          if (! yield)
            raise AssertionFailedError.new(message.to_s)
          end
        end
      end

      ##
      # Asserts that +boolean+ is not false or nil.
      #
      # Example:
      #   assert [1, 2].include?(5)

      public
      def assert(boolean, message=nil)
        _wrap_assertion do
          assert_block("assert should not be called with a block.") { !block_given? }
          assert_block(build_message(message, "<?> is not true.", boolean)) { boolean }
        end
      end

_wrap_assertion は入れ子になることもあるが、add_assertion を呼び出すのは一番外側だけだ。

Test::Unit::Assertions においては add_assertion の実装は空だが、コメントにある通り、これを include している Test::Unit::TestCase の側で add_assertion が適切にオーバーライドされ、assertion の数をカウントするようになっているはずだ。

Test::Unit::TestCase の該当個所を探す。

# /usr/lib/ruby/1.8/test/unit/testcase.rb

      def add_assertion
        @_result.add_assertion
      end
      private :add_assertion

@_result の中身は Test::Unit::TestResult で、add_assertion の実装は以下の通り。

# /usr/lib/ruby/1.8/test/unit/testresult.rb

      # Records an individual assertion.
      def add_assertion
        @assertion_count += 1
        notify_listeners(CHANGED, self)
      end
まとめ
  • 共通の前処理、後処理のセット → wrapper(&block)
  • コールバック