在Ruby的Test :: Unit :: TestCase中,如何覆盖initialize方法?

时间:2008-11-01 19:08:10

标签: ruby unit-testing testunit

我正在与Test :: Unit挣扎。当我想到单元测试时,我想到了每个文件的一个简单测试。但是在Ruby的框架中,我必须写一下:

class MyTest < Test::Unit::TestCase 
   def setup 
   end

   def test_1 
   end

   def test_1 
   end
end

但是每次调用test_ *方法时都会运行setup和teardown。这正是我不想要的。相反,我想要一个只为整个类运行一次的设置方法。但我似乎无法在不破坏TestCase初始化的情况下编写自己的initialize()。

这可能吗?还是我让这无可救药地变得复杂?

10 个答案:

答案 0 :(得分:25)

正如Hal Fulton的书“The Ruby Way”中所提到的那样。 他重写了Test :: Unit的self.suite方法,它允许类中的测试用例作为套件运行。

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

以下是一个例子:

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

答案 1 :(得分:9)

它应该如何运作!

每个测试应与其余测试完全隔离,因此每个测试用例都会执行一次setuptear_down方法。但是,有些情况下,您可能希望更多地控制执行流程。然后,您可以将测试用例分组到 suites

在您的情况下,您可以编写如下内容:

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

TestDecorator定义了一个特殊套件,它提供了setuptear_down方法,该方法在运行它包含的一组测试用例之前和之后只运行一次。

这样做的缺点是你需要告诉 Test :: Unit 如何在单元中运行测试。如果您的单元包含许多测试用例,并且您只需要一个装饰器,那么您需要这样的东西:

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)

Test::Unit documentation文档提供了有关套件如何工作的良好解释。

答案 2 :(得分:7)

最后,测试单元实现了这一点!活泉! 如果您使用的是v 2.5.2或更高版本,则可以使用:

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

当您开始测试时,这将运行一次。除了在每次测试(设置)之前运行的回调之外,还有在每个测试用例(启动)开始时运行的回调。

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

答案 3 :(得分:2)

嗯,我以一种非常丑陋和可怕的方式完成了基本相同的方式,但它更快。 :)一旦我意识到测试是按字母顺序运行的:

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

它很漂亮,但它有效:)

答案 4 :(得分:2)

为了解决这个问题,我使用了setup结构,只使用了一种测试方法。这一个测试方法正在调用所有其他测试。

例如

class TC_001 << Test::Unit::TestCase
  def setup
    # do stuff once
  end

  def testSuite
    falseArguments()
    arguments()
  end

  def falseArguments
    # do stuff
  end

  def arguments
    # do stuff
  end
end

答案 5 :(得分:2)

我知道这是一个相当古老的帖子,但我遇到了问题(并且已经使用Tes / unit编写过课程)并且使用其他方法回答了问题,所以如果它有帮助...

如果您只需要等效的启动函数,则可以使用类变量:

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 :(得分:1)

我遇到了这个确切的问题并创建了一个Test::Unit::TestCase的子类来完成您所描述的内容。

这就是我想出的。它提供了自己的setupteardown方法,用于计算类中以'test'开头的方法数。在第一次拨打setup时,系统会调用global_setup,并在最后一次拨打teardown时拨打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

像这样创建测试用例:

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

除非您使用setup类方法(仅适用于Rails 2.X),否则您无法提供自己的每次测试teardownsetup :method_name方法。 ?)如果你有一个测试套件或只运行其中一种测试方法的东西,那么global_teardown将不会被调用,因为它假定所有测试方法最终都会运行。

答案 7 :(得分:0)

使用TestSuite作为@ romulo-a-ceccon描述每个测试套件的特殊准备。

但是我认为应该在这里提到单元测试是完全隔离的。因此,执行流程是setup-test-teardown,它应该保证每个测试都不受其他测试所做的任何干扰。

答案 8 :(得分:0)

我创建了一个名为SetupOnce的mixin。这是使用它的一个例子。

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

这是实际的代码;请注意,它需要脚注中第一个链接提供的另一个模块。

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

脚注:

  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/

答案 9 :(得分:0)

@ orion-edwards上面的RSpec答案+1。我会对他的回答发表评论,但我还没有足够的声誉对答案发表评论。

我经常使用test / unit RSpec,我不得不说......每个人都发布的代码缺少{em>非常的重要功能{{ 1}}这是:@instance变量支持。

在RSpec中,你可以这样做:

before(:all)

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 #startup的实现首先集中在确保只为整个#shutdown类调用这些方法一次,但这些方法中使用的任何实例变量都是丢失!

RSpec在其自己的Object实例中运行其TestCase,并在运行每个测试之前复制所有局部变量。

要访问在全局before(:all)方法期间创建的任何变量,您需要:

  • 复制#startup创建的所有实例变量,如RSpec
  • #startup中的变量定义到您可以从测试方法中访问的范围,例如。 #startup或创建类级别的attr_accessors,以提供您在@@class_variables
  • 内创建的@instance_variables的访问权限

只需我0.02美元!