在 Ruby 的 Test::Unit::TestCase 中,如何重写初始化方法?

发布于 2024-07-08 22:25:57 字数 365 浏览 9 评论 0原文

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

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

   def test_1 
   end

   def test_1 
   end
end

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

那可能吗? 还是我让这件事变得极其复杂?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

空气里的味道 2024-07-15 22:25:57

正如 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

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
菩提树下叶撕阳。 2024-07-15 22:25:57

最终,测试单元已实现此功能! 哇!
如果您使用的是 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

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

别在捏我脸啦 2024-07-15 22:25:57

这就是它应该如何工作的!

每个测试都应该与其他测试完全隔离,因此每个测试用例的 setuptear_down 方法都会执行一次。 然而,在某些情况下,您可能希望对执行流程有更多控制。 然后您可以将测试用例分组到套件中。

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

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 文档 文档提供了有关套件如何工作的良好解释。

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.

宁愿没拥抱 2024-07-15 22:25:57

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

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

它并不漂亮,但它有效:)

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

灼痛 2024-07-15 22:25:57

为了解决这个问题,我使用了设置构造,仅遵循一种测试方法。 这一测试方法正在调用所有其他测试。

例如

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

To solve this problem I used the setup construct, with only one test method followed. This one testmethod is calling all other tests.

For instance

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
随遇而安 2024-07-15 22:25:57

我知道这是一篇相当老的帖子,但我遇到了这个问题(并且已经使用 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

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
讽刺将军 2024-07-15 22:25:57

我遇到了这个确切的问题,并创建了 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

这种情况的错误是您无法提供自己的每个测试 setupteardown 方法,除非您使用 setup :method_name 类方法(仅在 Rails 2.X 中可用?),如果您有一个测试套件或仅运行其中一个测试方法的东西,则不会调用 global_teardown 因为它假设最终将运行所有测试方法。

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.

无人接听 2024-07-15 22:25:57

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

但是我认为这里应该提到单元测试是完全隔离运行的。 因此,执行流程是设置-测试-拆卸,这应该保证每个测试的运行不受其他测试所做的任何事情的干扰。

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.

穿越时光隧道 2024-07-15 22:25:57

我创建了一个名为 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-c​​lasses-name/

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/

如果没结果 2024-07-15 22:25:57

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

我经常使用 test/unit 和 RSpec,我不得不说...每个人发布的代码都缺少 before( 的一个非常 重要功能:all) 即:@instance 变量支持。

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

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 的实现首先侧重于确保这些方法在整个 TestCase< 中仅被调用一次/code> 类,但是这些方法中使用的任何实例变量都会丢失!

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

要访问在全局 #startup 方法期间创建的任何变量,您需要:

  • 复制由 #startup 创建的所有实例变量,就像 RSpec 定义
  • 您的变量 一样将 #startup 中的变量放入您可以从测试方法访问的范围内,例如 @@class_variables 或创建类级 attr_accessors,提供对您在 def self.startup 内部创建的 @instance_variables 的访问,

只需我的 0.02 美元!

+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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文