In Ruby's Test::Unit::TestCase, how do I overr

2019-01-16 06:29发布

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 MyTest < Test::Unit::TestCase 
   def setup 
   end

   def test_1 
   end

   def test_1 
   end
end

But setup and teardown run for every invocation of a test_* method. This is exactly what I don't want. Rather, I want a setup method that runs just once for the whole class. But I can't seem to write my own initialize() without breaking TestCase's initialize.

Is that possible? Or am I making this hopelessly complicated?

10条回答
姐就是有狂的资本
2楼-- · 2019-01-16 06:29

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
查看更多
3楼-- · 2019-01-16 06:34

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/

查看更多
狗以群分
4楼-- · 2019-01-16 06:35

I came across this exact problem and created a subclass of Test::Unit::TestCase for doing exactly what you describe.

Here's what I came up with. It provides it's own setup and teardown methods that count the number of methods in the class that begin with 'test'. On the first call to setup it calls global_setup and on the last call to teardown it calls global_teardown

class ImprovedUnitTestCase < Test::Unit::TestCase
  cattr_accessor :expected_test_count

  def self.global_setup; end
  def self.global_teardown; end    

  def teardown
    if((self.class.expected_test_count-=1) == 0)
      self.class.global_teardown
    end
  end
  def setup
    cls = self.class

    if(not cls.expected_test_count)
      cls.expected_test_count = (cls.instance_methods.reject{|method| method[0..3] != 'test'}).length
      cls.global_setup
    end
  end
end

Create your test cases like this:

class TestSomething < ImprovedUnitTestCase
  def self.global_setup
    puts 'global_setup is only run once at the beginning'
  end

  def self.global_teardown
    puts 'global_teardown is only run once at the end'
  end

  def test_1 
  end

  def test_2
  end
end

The fault in this is that you can't provide your own per-test setup and teardown methods unless you use the setup :method_name class method (only available in Rails 2.X?) and if you have a test suite or something that only runs one of the test methods, then the global_teardown won't be called because it assumes that all the test methods will be run eventually.

查看更多
一夜七次
5楼-- · 2019-01-16 06:47

I know this is quite an old post, but I had the issue (and had already written classes using Tes/unit) and ave answered using another method, so if it can help...

If you only need the equivalent of the startup function, you can use the class variables:

class MyTest < Test::Unit::TestCase
  @@cmptr = nil
  def setup
    if @@cmptr.nil?
      @@cmptr = 0
      puts "runs at first test only"
      @@var_shared_between_fcs = "value"
    end
    puts 'runs before each test'
  end
  def test_stuff
    assert(true)
  end
end
查看更多
虎瘦雄心在
6楼-- · 2019-01-16 06:51

FINALLY, test-unit has this implemented! Woot! If you are using v 2.5.2 or later, you can just use this:

Test::Unit.at_start do
  # initialization stuff here
end

This will run once when you start your tests off. There are also callbacks which run at the beginning of each test case (startup), in addition to the ones that run before every test (setup).

http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method

查看更多
闹够了就滚
7楼-- · 2019-01-16 06:51

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 :)

查看更多
登录 后发表回答