lib/test/unit/testcase.rb in test-unit-2.2.0 vs lib/test/unit/testcase.rb in test-unit-2.3.0
- old
+ new
@@ -1,11 +1,11 @@
#--
#
# Author:: Nathaniel Talbott.
# Copyright::
# * Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
-# * Copyright (c) 2008-2009 Kouhei Sutou <tt><kou@clear-code.com></tt>
+# * Copyright (c) 2008-2011 Kouhei Sutou <tt><kou@clear-code.com></tt>
# License:: Ruby license.
require 'test/unit/attribute'
require 'test/unit/fixture'
require 'test/unit/exceptionhandler'
@@ -14,11 +14,13 @@
require 'test/unit/error'
require 'test/unit/pending'
require 'test/unit/omission'
require 'test/unit/notification'
require 'test/unit/priority'
+require 'test/unit/data'
require 'test/unit/testsuite'
+require 'test/unit/testsuitecreator'
require 'test/unit/assertionfailederror'
require 'test/unit/util/backtracefilter'
require 'test/unit/util/output'
require 'test/unit/util/method-owner-finder'
@@ -47,10 +49,14 @@
#
# def setup
# ...
# end
#
+ # def cleanup
+ # ...
+ # end
+ #
# def teardown
# ...
# end
#
# def test_my_method1
@@ -64,13 +70,15 @@
#
# Here is a call order:
# * startup
# * setup
# * test_my_method1
+ # * cleanup
# * teardown
# * setup
# * test_my_method2
+ # * cleanup
# * teardown
# * shutdown
class TestCase
include Attribute
include Fixture
@@ -79,16 +87,19 @@
include FailureHandler
include TestCasePendingSupport
include TestCaseOmissionSupport
include TestCaseNotificationSupport
include Priority
+ include Data
include Assertions
include Util::BacktraceFilter
include Util::Output
STARTED = name + "::STARTED" # :nodoc:
FINISHED = name + "::FINISHED" # :nodoc:
+ STARTED_OBJECT = name + "::STARTED::OBJECT" # :nodoc:
+ FINISHED_OBJECT = name + "::FINISHED::OBJECT" # :nodoc:
DESCENDANTS = [] # :nodoc:
AVAILABLE_ORDERS = [:alphabetic, :random, :defined] # :nodoc:
class << self
@@ -97,34 +108,28 @@
end
@@added_methods = {}
def method_added(name) # :nodoc:
super
- added_methods = (@@added_methods[self] ||= [])
+ _added_methods = added_methods
stringified_name = name.to_s
- if added_methods.include?(stringified_name)
+ if _added_methods.include?(stringified_name)
attribute(:redefined, true, {}, stringified_name)
end
- added_methods << stringified_name
+ _added_methods << stringified_name
end
+ def added_methods # :nodoc:
+ @@added_methods[self] ||= []
+ end
+
# Rolls up all of the test* methods in the fixture into
# one suite, creating a new instance of the fixture for
# each method.
def suite
- suite = TestSuite.new(name, self)
- collect_test_names.each do |test|
- catch(:invalid_test) do
- suite << new(test)
- end
- end
- if suite.empty?
- catch(:invalid_test) do
- suite << new("default_test")
- end
- end
- suite
+ suite_creator = TestSuiteCreator.new(self)
+ suite_creator.create
end
# Called before every test case runs. Can be used
# to set up fixture information used in test case
# scope.
@@ -217,27 +222,51 @@
# Tests are sorted in defined order.
def test_order=(order)
@@test_orders[self] = order
end
- # Defines a test in declarative syntax.
+ # Defines a test in declarative syntax or marks
+ # following method as a test method.
#
- # The following two test definitions are the same:
+ # In declarative syntax usage, the following two
+ # test definitions are the almost same:
#
# description "register user"
# def test_register_user
# ...
# end
#
# test "register user" do
# ...
# end
- def test(test_description, &block)
- normalized_description = test_description.gsub(/[^a-zA-Z\d_]+/, '_')
- method_name = "test_#{normalized_description}".to_sym
- define_method(method_name, &block)
- description(test_description, method_name)
+ #
+ # In test method mark usage, the "my_test_method" is
+ # treated as a test method:
+ #
+ # test
+ # def my_test_method
+ # assert_equal("call me", ...)
+ # end
+ def test(*test_description_or_targets, &block)
+ if block_given?
+ test_description = test_description_or_targets.first
+ if test_description.nil?
+ raise ArgumentError, "test description is missing"
+ end
+ n_arguments = test_description_or_targets.size
+ if n_arguments > 1
+ message = "wrong number of arguments (#{n_arguments} for 1)"
+ raise ArgumentError, message
+ end
+ method_name = test_description
+ define_method(method_name, &block)
+ description(test_description, method_name)
+ attribute(:test, true, {}, method_name)
+ else
+ targets = test_description_or_targets
+ attribute(:test, true, {}, *targets)
+ end
end
# Describes a test.
#
# The following example associates "register a
@@ -249,89 +278,70 @@
# ...
# end
def description(value, target=nil)
attribute(:description, value, {}, target || [])
end
-
- # :stopdoc:
- private
- def collect_test_names
- method_names = public_instance_methods(true).collect do |name|
- name.to_s
- end
- test_names = method_names.find_all do |method_name|
- method_name =~ /^test./
- end
- send("sort_test_names_in_#{test_order}_order", test_names)
- end
-
- def sort_test_names_in_alphabetic_order(test_names)
- test_names.sort
- end
-
- def sort_test_names_in_random_order(test_names)
- test_names.sort_by {rand(test_names.size)}
- end
-
- def sort_test_names_in_defined_order(test_names)
- added_methods = @@added_methods[self]
- test_names.sort do |test1, test2|
- test1_defined_order = added_methods.index(test1)
- test2_defined_order = added_methods.index(test2)
- if test1_defined_order and test2_defined_order
- test1_defined_order <=> test2_defined_order
- elsif test1_defined_order
- 1
- elsif test2_defined_order
- -1
- else
- test1 <=> test2
- end
- end
- end
- # :startdoc:
end
attr_reader :method_name
# Creates a new instance of the fixture for running the
# test represented by test_method_name.
def initialize(test_method_name)
- throw :invalid_test unless respond_to?(test_method_name)
- test_method = method(test_method_name)
- throw :invalid_test if test_method.arity > 0
- owner = Util::MethodOwnerFinder.find(self, test_method_name)
+ @method_name = test_method_name
+ @internal_data = InternalData.new
+ end
+
+ # Assigns test data to the test. It is used in internal.
+ def assign_test_data(label, data) # :nodoc:
+ @internal_data.assign_test_data(label, data)
+ end
+
+ # Returns the test is valid test. It is used in internal.
+ def valid? # :nodoc:
+ return false unless respond_to?(@method_name)
+ test_method = method(@method_name)
+ if @internal_data.have_test_data?
+ return false unless test_method.arity == 1
+ else
+ return false unless test_method.arity <= 0
+ end
+ owner = Util::MethodOwnerFinder.find(self, @method_name)
if owner.class != Module and self.class != owner
- throw :invalid_test
+ return false
end
- @method_name = test_method_name
- @test_passed = true
- @interrupted = false
+ true
end
# Runs the individual test method represented by this
# instance of the fixture, collecting statistics, failures
# and errors in result.
def run(result)
begin
@_result = result
+ @internal_data.test_started
yield(STARTED, name)
+ yield(STARTED_OBJECT, self)
begin
run_setup
run_test
+ run_cleanup
+ add_pass
rescue Exception
- @interrupted = true
+ @internal_data.interrupted
raise unless handle_exception($!)
ensure
begin
run_teardown
rescue Exception
raise unless handle_exception($!)
end
end
+ @internal_data.test_finished
result.add_run
yield(FINISHED, name)
+ yield(FINISHED_OBJECT, self)
ensure
# @_result = nil # For test-spec's after_all :<
end
end
@@ -366,10 +376,45 @@
# * my_setup2
# * test_my_class
def setup
end
+ # Called after every test method runs but the test
+ # method isn't marked as 'passed'. Can be used to
+ # clean up and/or verify tested condition.
+ # e.g. Can be used to verify mock.
+ #
+ # You can add additional cleanup tasks by the following
+ # code:
+ # class TestMyClass < Test::Unit::TestCase
+ # def cleanup
+ # ...
+ # end
+ #
+ # cleanup
+ # def my_cleanup1
+ # ...
+ # end
+ #
+ # cleanup
+ # def my_cleanup2
+ # ...
+ # end
+ #
+ # def test_my_class
+ # ...
+ # end
+ # end
+ #
+ # Here is a call order:
+ # * test_my_class
+ # * my_cleanup2
+ # * my_cleanup1
+ # * cleanup
+ def cleanup
+ end
+
# Called after every test method runs. Can be used to tear
# down fixture information.
#
# You can add additional teardown tasks by the following
# code:
@@ -407,14 +452,25 @@
def size
1
end
+ # Returns a label of test data for the test. If the
+ # test isn't associated with any test data, it returns
+ # +nil+.
+ def data_label
+ @internal_data.test_data_label
+ end
+
# Returns a human-readable name for the specific test that
# this instance of TestCase represents.
def name
- "#{@method_name}(#{self.class.name})"
+ if @internal_data.have_test_data?
+ "#{@method_name}[#{data_label}](#{self.class.name})"
+ else
+ "#{@method_name}(#{self.class.name})"
+ end
end
# Returns a description for the test. A description
# will be associated by Test::Unit::TestCase.test or
# Test::Unit::TestCase.description.
@@ -429,55 +485,115 @@
name
end
# It's handy to be able to compare TestCase instances.
def ==(other)
- return false unless(other.kind_of?(self.class))
- return false unless(@method_name == other.method_name)
+ return false unless other.kind_of?(self.class)
+ return false unless @method_name == other.method_name
self.class == other.class
end
+ # Returns a Time at the test was started.
+ def start_time
+ @internal_data.start_time
+ end
+
+ # Returns elapsed time for the test was ran.
+ def elapsed_time
+ @internal_data.elapsed_time
+ end
+
+ # Returns whether the test is interrupted.
def interrupted?
- @interrupted
+ @internal_data.interrupted?
end
+ # Returns whether this individual test passed or
+ # not. Primarily for use in teardown so that artifacts
+ # can be left behind if the test fails.
+ def passed?
+ @internal_data.passed?
+ end
+
private
def current_result
@_result
end
def run_test
- if self.class.get_attribute(@method_name, :redefined)
+ if self[:redefined]
notify("#{self.class}\##{@method_name} was redefined")
end
- __send__(@method_name)
- add_pass
+ if @internal_data.have_test_data?
+ __send__(@method_name, @internal_data.test_data)
+ else
+ __send__(@method_name)
+ end
end
def handle_exception(exception)
self.class.exception_handlers.each do |handler|
return true if send(handler, exception)
end
false
end
- # Returns whether this individual test passed or
- # not. Primarily for use in teardown so that artifacts
- # can be left behind if the test fails.
- def passed?
- @test_passed
- end
-
def problem_occurred
- @test_passed = false
+ @internal_data.problem_occurred
end
def add_assertion
current_result.add_assertion
end
def add_pass
current_result.add_pass
+ end
+
+ class InternalData
+ attr_reader :start_time, :elapsed_time
+ attr_reader :test_data_label, :test_data
+ def initialize
+ @start_time = nil
+ @elapsed_time = nil
+ @passed = true
+ @interrupted = false
+ @test_data_label = nil
+ @test_data = nil
+ end
+
+ def passed?
+ @passed
+ end
+
+ def interrupted?
+ @interrupted
+ end
+
+ def assign_test_data(label, data)
+ @test_data_label = label
+ @test_data = data
+ end
+
+ def have_test_data?
+ not @test_data_label.nil?
+ end
+
+ def test_started
+ @start_time = Time.now
+ end
+
+ def test_finished
+ @elapsed_time = Time.now - @start_time
+ end
+
+ def problem_occurred
+ @passed = false
+ end
+
+ def interrupted
+ @interrupted = true
+ end
end
end
end
end