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