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

时间:2023-01-15 21:55:32

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:

我在测试:单元。当我想到单元测试时,我想到的是每个文件一个简单的测试。但是在Ruby的框架中,我必须这样写:

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.

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

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

这有可能吗?还是我把事情弄得太复杂了?

10 个解决方案

#1


25  

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.

正如哈尔·富尔顿在《Ruby之路》一书中提到的那样。他重写了自我。测试的套件方法::允许一个类中的测试用例作为一个套件运行的单元。

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

#2


9  

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.

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

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.

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

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:

这样做的缺点是您需要告诉Test::Unit how to run The testing in The Unit。如果您的单元包含许多测试用例,并且您只需要其中一个decorator,那么您将需要如下内容:

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.

测试::单元文档文档提供了一个关于套件如何工作的很好的解释。

#3


7  

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

最后,测试单元实现了这一点!哇!如果您正在使用v2.5.2或更高版本,您可以使用以下内容:

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

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

#4


2  

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

它虽然不漂亮,但很管用:

#5


2  

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

为了解决这个问题,我使用了setup构造,后面只有一个测试方法。这个testmethod调用所有其他的测试。

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

#6


2  

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

我知道这是一个相当古老的帖子,但是我有这个问题(并且已经用Tes/unit编写了类),而且ave使用了另一个方法来回答这个问题,所以如果可以的话……

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

#7


1  

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

我遇到了这个问题,并创建了Test的子类:Unit::TestCase来完成您描述的工作。

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

这是我想到的。它提供了自己的设置和拆解方法,用来计算以“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

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.

错的是你不能提供自己的每个测试设置和清除方法,除非你使用的设置:method_name类方法(仅可在Rails 2. x ?)如果你有一个测试套件或只运行一个测试方法,然后global_teardown不会被调用,因为它假设最终将运行所有的测试方法。

#8


0  

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

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

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.

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

#9


0  

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

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

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

    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/

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

#10


0  

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

+1是@orion-edwards给出的RSpec答案。我本想评论他的回答,但我还没有足够的声誉来评论答案。

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.

我经常使用测试/单元和RSpec,我不得不说……每个人发布的代码都缺少before(:all)的一个非常重要的特性:@instance变量支持。

In RSpec, you can do:

在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

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!

#startup和#shutdown的实现首先关注的是确保这些方法只在整个TestCase类中被调用一次,但是这些方法中使用的任何实例变量都将丢失!

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

RSpec在自己的对象实例中运行before(:all),在运行每个测试之前复制所有本地变量。

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
  • 复制#startup创建的所有实例变量,就像RSpec那样
  • 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
  • 在#startup中定义变量到可以从测试方法访问的范围中,例如。@@class_variables或创建类级attr_accessors,它们提供对在def self.startup中创建的@instance_variables的访问

Just my $0.02!

只是我的0.02美元!

#1


25  

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.

正如哈尔·富尔顿在《Ruby之路》一书中提到的那样。他重写了自我。测试的套件方法::允许一个类中的测试用例作为一个套件运行的单元。

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

#2


9  

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.

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

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.

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

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:

这样做的缺点是您需要告诉Test::Unit how to run The testing in The Unit。如果您的单元包含许多测试用例,并且您只需要其中一个decorator,那么您将需要如下内容:

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.

测试::单元文档文档提供了一个关于套件如何工作的很好的解释。

#3


7  

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

最后,测试单元实现了这一点!哇!如果您正在使用v2.5.2或更高版本,您可以使用以下内容:

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

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

#4


2  

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

它虽然不漂亮,但很管用:

#5


2  

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

为了解决这个问题,我使用了setup构造,后面只有一个测试方法。这个testmethod调用所有其他的测试。

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

#6


2  

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

我知道这是一个相当古老的帖子,但是我有这个问题(并且已经用Tes/unit编写了类),而且ave使用了另一个方法来回答这个问题,所以如果可以的话……

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

#7


1  

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

我遇到了这个问题,并创建了Test的子类:Unit::TestCase来完成您描述的工作。

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

这是我想到的。它提供了自己的设置和拆解方法,用来计算以“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

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.

错的是你不能提供自己的每个测试设置和清除方法,除非你使用的设置:method_name类方法(仅可在Rails 2. x ?)如果你有一个测试套件或只运行一个测试方法,然后global_teardown不会被调用,因为它假设最终将运行所有的测试方法。

#8


0  

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

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

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.

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

#9


0  

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

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

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

    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/

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

#10


0  

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

+1是@orion-edwards给出的RSpec答案。我本想评论他的回答,但我还没有足够的声誉来评论答案。

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.

我经常使用测试/单元和RSpec,我不得不说……每个人发布的代码都缺少before(:all)的一个非常重要的特性:@instance变量支持。

In RSpec, you can do:

在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

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!

#startup和#shutdown的实现首先关注的是确保这些方法只在整个TestCase类中被调用一次,但是这些方法中使用的任何实例变量都将丢失!

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

RSpec在自己的对象实例中运行before(:all),在运行每个测试之前复制所有本地变量。

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
  • 复制#startup创建的所有实例变量,就像RSpec那样
  • 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
  • 在#startup中定义变量到可以从测试方法访问的范围中,例如。@@class_variables或创建类级attr_accessors,它们提供对在def self.startup中创建的@instance_variables的访问

Just my $0.02!

只是我的0.02美元!