In Ruby's Test::Unit::TestCase, how do I override the initialize method?

前端 未结 10 1802
再見小時候
再見小時候 2020-12-07 14:54

I\'m struggling with Test::Unit. When I think of unit tests, I think of one simple test per file. But in Ruby\'s framework, I must instead write:

class M         


        
相关标签:
10条回答
  • 2020-12-07 15:20

    +1 for the RSpec answer above by @orion-edwards. I would have commented on his answer, but I don't have enough reputation yet to comment on answers.

    I use test/unit and RSpec a lot and I have to say ... the code that everyone has been posting is missing a very important feature of before(:all) which is: @instance variable support.

    In RSpec, you can do:

    describe 'Whatever' do
      before :all do
        @foo = 'foo'
      end
    
      # This will pass
      it 'first' do
        assert_equal 'foo', @foo
        @foo = 'different'
        assert_equal 'different', @foo
      end
    
      # This will pass, even though the previous test changed the 
      # value of @foo.  This is because RSpec stores the values of 
      # all instance variables created by before(:all) and copies 
      # them into your test's scope before each test runs.
      it 'second' do
        assert_equal 'foo', @foo
        @foo = 'different'
        assert_equal 'different', @foo
      end
    end
    

    The implementations of #startup and #shutdown above all focus on making sure that these methods only get called once for the entire TestCase class, but any instance variables used in these methods would be lost!

    RSpec runs its before(:all) in its own instance of Object and all of the local variables are copied before each test is run.

    To access any variables that are created during a global #startup method, you would need to either:

    • copy all of the instance variables created by #startup, like RSpec does
    • define your variables in #startup into a scope that you can access from your test methods, eg. @@class_variables or create class-level attr_accessors that provide access to the @instance_variables that you create inside of def self.startup

    Just my $0.02!

    0 讨论(0)
  • 2020-12-07 15:21

    Use the TestSuite as @romulo-a-ceccon described for special preparations for each test suite.

    However I think it should be mentioned here that Unit tests are ment to run in total isolation. Thus the execution flow is setup-test-teardown which should guarantee that each test run undisturbed by anything the other tests did.

    0 讨论(0)
  • 2020-12-07 15:22

    I created a mixin called SetupOnce. Here's an example of using it.

    require 'test/unit'
    require 'setuponce'
    
    
    class MyTest < Test::Unit::TestCase
      include SetupOnce
    
      def self.setup_once
        puts "doing one-time setup"
      end
    
      def self.teardown_once
        puts "doing one-time teardown"
      end
    
    end
    

    And here is the actual code; notice it requires another module available from the first link in the footnotes.

    require 'mixin_class_methods' # see footnote 1
    
    module SetupOnce
      mixin_class_methods
    
      define_class_methods do
        def setup_once; end
    
        def teardown_once; end
    
        def suite
          mySuite = super
    
          def mySuite.run(*args)
            @name.to_class.setup_once
            super(*args)
            @name.to_class.teardown_once
          end
    
          return mySuite
        end
      end
    end
    
    # See footnote 2
    class String
      def to_class
        split('::').inject(Kernel) {
          |scope, const_name|
          scope.const_get(const_name)
        }
      end
    end
    

    Footnotes:

    1. http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html

    2. http://infovore.org/archives/2006/08/02/getting-a-class-object-in-ruby-from-a-string-containing-that-classes-name/

    0 讨论(0)
  • 2020-12-07 15:23

    As mentioned in Hal Fulton's book "The Ruby Way". He overrides the self.suite method of Test::Unit which allows the test cases in a class to run as a suite.

    def self.suite
        mysuite = super
        def mysuite.run(*args)
          MyTest.startup()
          super
          MyTest.shutdown()
        end
        mysuite
    end
    

    Here is an example:

    class MyTest < Test::Unit::TestCase
        class << self
            def startup
                puts 'runs only once at start'
            end
            def shutdown
                puts 'runs only once at end'
            end
            def suite
                mysuite = super
                def mysuite.run(*args)
                  MyTest.startup()
                  super
                  MyTest.shutdown()
                end
                mysuite
            end
        end
    
        def setup
            puts 'runs before each test'
        end
        def teardown
            puts 'runs after each test'
        end 
        def test_stuff
            assert(true)
        end
    end
    
    0 讨论(0)
  • 2020-12-07 15:24

    That's how it's supposed to work!

    Each test should be completely isolated from the rest, so the setup and tear_down methods are executed once for every test-case. There are cases, however, when you might want more control over the execution flow. Then you can group the test-cases in suites.

    In your case you could write something like the following:

    require 'test/unit'
    require 'test/unit/ui/console/testrunner'
    
    class TestDecorator < Test::Unit::TestSuite
    
      def initialize(test_case_class)
        super
        self << test_case_class.suite
      end
    
      def run(result, &progress_block)
        setup_suite
        begin
          super(result, &progress_block)      
        ensure
          tear_down_suite
        end
      end
    
    end
    
    class MyTestCase < Test::Unit::TestCase
    
      def test_1
        puts "test_1"
        assert_equal(1, 1)
      end
    
      def test_2
        puts "test_2"
        assert_equal(2, 2)
      end
    
    end
    
    class MySuite < TestDecorator
    
      def setup_suite
        puts "setup_suite"
      end
    
      def tear_down_suite
        puts "tear_down_suite"
      end
    
    end
    
    Test::Unit::UI::Console::TestRunner.run(MySuite.new(MyTestCase))
    

    The TestDecorator defines a special suite which provides a setup and tear_down method which run only once before and after the running of the set of test-cases it contains.

    The drawback of this is that you need to tell Test::Unit how to run the tests in the unit. In the event your unit contains many test-cases and you need a decorator for only one of them you'll need something like this:

    require 'test/unit'
    require 'test/unit/ui/console/testrunner'
    
    class TestDecorator < Test::Unit::TestSuite
    
      def initialize(test_case_class)
        super
        self << test_case_class.suite
      end
    
      def run(result, &progress_block)
        setup_suite
        begin
          super(result, &progress_block)      
        ensure
          tear_down_suite
        end
      end
    
    end
    
    class MyTestCase < Test::Unit::TestCase
    
      def test_1
        puts "test_1"
        assert_equal(1, 1)
      end
    
      def test_2
        puts "test_2"
        assert_equal(2, 2)
      end
    
    end
    
    class MySuite < TestDecorator
    
      def setup_suite
        puts "setup_suite"
      end
    
      def tear_down_suite
        puts "tear_down_suite"
      end
    
    end
    
    class AnotherTestCase < Test::Unit::TestCase
    
      def test_a
        puts "test_a"
        assert_equal("a", "a")
      end
    
    end
    
    class Tests
    
      def self.suite
        suite = Test::Unit::TestSuite.new
        suite << MySuite.new(MyTestCase)
        suite << AnotherTestCase.suite
        suite
      end
    
    end
    
    Test::Unit::UI::Console::TestRunner.run(Tests.suite)
    

    The Test::Unit documentation documentation provides a good explanation on how suites work.

    0 讨论(0)
  • 2020-12-07 15:25

    Well, I accomplished basically the same way in a really ugly and horrible fashion, but it was quicker. :) Once I realized that the tests are run alphabetically:

    class MyTests < Test::Unit::TestCase
    def test_AASetup # I have a few tests that start with "A", but I doubt any will start with "Aardvark" or "Aargh!"
        #Run setup code
    end
    
    def MoreTests
    end
    
    def test_ZTeardown
        #Run teardown code
    end
    

    It aint pretty, but it works :)

    0 讨论(0)
提交回复
热议问题