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

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

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

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

那可能吗? 还是我让这绝望地变得复杂?

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

这就是它应该如何工作!

每个测试应该与其余测试完全隔离,因此每个测试用例都会执行一次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文档文档提供了有关套件如何工作的良好解释。

最后,测试单元实现了这一点! 活泉! 如果您使用的是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

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

 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 

它不漂亮,但它的工作:)

为了解决这个问题,我使用了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 

我知道这是一个很老的post,但是我有问题(并且已经使用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 

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

这里的错误是你不能提供你自己的per-test setupteardown方法,除非你使用setup :method_name类方法(仅在Rails 2.X中可用?),如果你有一个测试套件或只有运行其中一个测试方法,然后不会调用global_teardown因为它假定最终将运行所有测试方法。

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

但是我认为应该在这里提到unit testing是完全隔离的。 因此,执行流程是设置 – 测试 – 拆除,这应该保证每个测试运行不会被其他测试所做的任何事情干扰。

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

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

我经常使用测试/单元 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类调用这些方法一次,但这些方法中使用的任何实例变量都将丢失!

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

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

  • 复制#startup创建的所有实例变量,就像RSpec一样
  • #startup的变量定义到可以从测试方法访问的范围,例如。 @@class_variables或创建类级别的attr_accessors,它们提供对在def self.startup创建的def self.startup

只需我0.02美元!