2008-11-01 66 views
26

我與測試::單位掙扎。當我想的單元測試,我想每個文件一個簡單的測試。但是在Ruby的框架,我必須改爲寫:在Ruby的Test :: Unit :: TestCase中,如何覆蓋initialize方法?

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

    def test_1 
    end 

    def test_1 
    end 
end 

但安裝和拆卸運行了TEST_ *方法每次調用。這正是我不想要的。相反,我想運行只有一次全班的設置方法。但我似乎無法編寫我自己的initialize()而沒有打破TestCase的初始化。

這可能嗎?還是我讓這個絕望變得複雜?

+0

兩種測試方法具有相同的名稱會導致沒有運行的第一個方法。你可以在第一次測試中放入一個失敗,測試仍然會通過。剪切和粘貼編程的一個副作用。 – 2009-02-18 01:00:19

+0

是的,而且很簡單。這最終在TestUnit中實現。看我的帖子waaaay在這個頁面。 – jpgeek 2012-09-25 14:59:20

回答

9

這就是它應該如何工作!

每個測試應該與其餘的完全隔離,因此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方法,只有前和測試 - 集的運行後,運行一次它包含的案例。

這樣做的缺點是,你需要告訴測試::單位如何運行的單元測試。倘若您的單位包含了很多的測試用例,你需要的只是其中的一個裝飾,你需要這樣的事:

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) 

的文檔提供了有關如何套房工作一個很好的解釋。

+0

爲什麼我得到一個錯誤「未初始化的常量Test :: Unit :: TestSuite」? – Alexandre 2012-09-30 17:14:40

1

我遇到了這個確切的問題,並創建了Test::Unit::TestCase的子類,以完成您所描述的內容。

這是我想出來的。它提供了自己的setupteardown方法來計算類中以'test'開頭的方法數量。在第一次調用setup它調用global_setup和上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 :method_name類方法(僅適用於Rails 2.X?),並且如果您有測試套件或僅運行其中一種測試方法的測試套件,則您可以提供自己的測試setupteardown方法,然後不會被調用,因爲它假定所有的測試方法都將最終運行。

0

使用TestSuite作爲@ romulo-a-ceccon描述爲每個測試套件進行特殊準備。

不過我覺得應該在這裏提到單元測試是完全隔離運行的。因此執行流程是setup-test-teardown,它應該保證每個測試都不會受到其他測試所做的任何事情的干擾。

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/

24

正如哈爾富爾頓的書 「Ruby之道」 中提到。 他重寫了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 
+0

謝謝你的回答! – 2009-05-27 10:57:42

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 

它是不是很漂亮,但它的工作原理:)

2

爲了解決這個問題,我使用的安裝結構,只有一個測試方法執行。這一個測試方法正在調用所有其他測試。

例如

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 
0

+1的RSpec的通過@獵戶座愛德華茲回答以上。我會評論他的回答,但我還沒有足夠的聲望評論答案。

我使用測試/單元 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 

上確保這些方法只能得到整個TestCase類調​​用一次,但是任何實例變量在這些已使用的#startup#shutdown首先重點實施方法將會丟失!

RSpec在其自己的Object實例中運行其before(:all),並且在每次運行測試之前複製所有本地變量。

以訪問全球#startup方法過程中產生的任何變量,你將需要:

  • 拷貝所有#startup創建的,像RSpec的實例變量的不
  • #startup定義的變量進入您可以從您的測試方法訪問的範圍,例如。 @@class_variables或創建類級attr_accessors提供訪問@instance_variables您創建裏面的def self.startup

只是我的$ 0.02!

2

我知道這是一個很老的文章,但我有問題(並且已經使用維護設備/單元編寫的類),並使用另一種方法,因此,如果它可以幫助AVE回答...

如果你只需要啓動功能相當於,你可以使用類變量:

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