用Ruby';s Test::Unit::TestCase,如何重写initialize方法?

用Ruby';s Test::Unit::TestCase,如何重写initialize方法?,ruby,unit-testing,testunit,Ruby,Unit Testing,Testunit,我正在与Test::Unit进行斗争。当我想到单元测试时,我想到的是每个文件一个简单的测试。但在Ruby的框架中,我必须写: class MyTest < Test::Unit::TestCase def setup end def test_1 end def test_1 end end classmytest

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

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

   def test_1 
   end

   def test_1 
   end
end
classmytest
但是每次调用test_*方法都会运行setup和teardown。这正是我不想要的。相反,我想要一个对整个类只运行一次的setup方法。但是如果不破坏TestCase的initialize,我似乎无法编写自己的initialize()


可能吗?还是我把这件事搞得太复杂了?

应该是这样的

每个测试都应该与其他测试完全隔离,因此对每个测试用例执行一次
设置
拆卸
方法。但是,在某些情况下,您可能需要对执行流进行更多控制。然后您可以将测试用例分组到套件中

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

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

文档很好地解释了套件是如何工作的。

我遇到了这个问题,并创建了
Test::Unit::TestCase
的子类,以实现您所描述的功能

这是我想到的。它提供了自己的
setup
teardown
方法,用于计算类中以“test”开头的方法的数量。第一次调用
setup
时,它调用
global\u setup
,最后一次调用
teardown
时,它调用
global\u 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 ImprovedUnitTestCase
像这样创建您的测试用例:

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

这其中的错误在于,除非您使用
setup:method\u name
class方法(仅在Rails 2.X中可用),并且如果您有一个测试套件或仅运行其中一个测试方法的东西,否则您无法提供自己的每测试
setup
teardown
方法,然后,将不会调用
全局\u teardown
,因为它假定所有的测试方法最终都将运行。

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


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

我创建了一个名为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
脚注:


  • 正如哈尔·富尔顿在《红宝石之路》一书中提到的那样。 他重写了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
    
    classmytest班级嗯,我基本上以同样的方式完成了一个非常丑陋和可怕的任务,但速度更快。:)一旦我意识到测试是按字母顺序运行的:

    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
    
    classmytests

    它虽然不漂亮,但很有效:)

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

    比如说

    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
    

    @orion edwards的上述RSpec答案的TC_001级+1。我本来会对他的答案发表评论的,但我还没有足够的声誉来评论他的答案

    我经常使用test/unit和RSpec,我不得不说。。。每个人发布的代码都缺少(:all)
    之前的一个非常重要的特性:@instance variable support

    在RSpec中,您可以执行以下操作:

    描述“无论做什么”
    以前:所有人都这样做
    @foo='foo'
    结束
    #这会过去的
    这是“第一个”做的
    断言_等于'foo',@foo
    @foo=‘不同’
    断言_等于‘不同’,@foo
    结束
    #即使之前的测试更改了
    #@foo的值。这是因为RSpec存储
    #由before(:all)和copies创建的所有实例变量
    #在每次测试运行之前,将它们放入测试的范围。
    这是“第二次”做什么
    断言_等于'foo',@foo
    @foo=‘不同’
    断言_等于‘不同’,@foo
    结束
    结束
    
    #startup
    #shutdown
    的实现首先关注于确保这些方法对于整个
    TestCase
    类只调用一次,但是这些方法中使用的任何实例变量都将丢失

    RSpec在自己的对象实例中运行(:all)
    之前的
    ,并且在运行每个测试之前复制所有局部变量

    要访问在全局启动过程中创建的任何变量,您需要:

    • 复制由
      #startup
      创建的所有实例变量,就像RSpec一样
    • #startup
      中的变量定义到一个可以从测试方法访问的范围内,例如
      @@class_variables
      或创建提供对
      的访问的类级attr_访问器
      
      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
      
      Test::Unit.at_start do
        # initialization stuff here
      end
      
      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