web-dev-qa-db-ja.com

RubyのTest :: Unit :: TestCaseで、initializeメソッドをオーバーライドするにはどうすればよいですか?

Test :: Unitで苦労しています。単体テストについて考えるとき、ファイルごとに1つの簡単なテストを考えます。しかし、Rubyのフレームワークでは、代わりに次のように書く必要があります。

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

   def test_1 
   end

   def test_1 
   end
end

ただし、セットアップとティアダウンは、test_ *メソッドを呼び出すたびに実行されます。これはまさに私が望まないことです。むしろ、クラス全体で1回だけ実行されるセットアップメソッドが必要です。しかし、TestCaseのinitializeを壊さずに、独自のinitialize()を作成することはできないようです。

それは可能ですか?それとも私はこれを絶望的に複雑にしていますか?

26
someguy

HalFultonの著書「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
26
Matt Wolfe

それが機能するはずです!

各テストは他のテストから完全に分離する必要があるため、setupメソッドとtear_downメソッドはテストケースごとに1回実行されます。ただし、実行フローをより細かく制御したい場合があります。次に、テストケースをsuitesにグループ化できます。

あなたの場合、あなたは次のようなものを書くことができます:

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は、含まれるテストケースのセットの実行の前後に1回だけ実行されるsetupおよびtear_downメソッドを提供する特別なスイートを定義します。

これの欠点は、ユニットでテストを実行する方法をTest :: Unitに指示する必要があることです。ユニットに多くのテストケースが含まれていて、そのうちの1つだけにデコレータが必要な場合は、次のようなものが必要になります。

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ドキュメント ドキュメントは、スイートがどのように機能するかについての良い説明を提供します。

10
Rômulo Ceccon

最後に、テストユニットにはこれが実装されています! Woot!v 2.5.2以降を使用している場合は、次のように使用できます。

Test::Unit.at_start do
  # initialization stuff here
end

これは、テストを開始するときに1回実行されます。すべてのテスト(セットアップ)の前に実行されるコールバックに加えて、各テストケースの開始(スタートアップ)で実行されるコールバックもあります。

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

9
jpgeek

この問題を解決するために、セットアップ構成を使用し、1つのテスト方法のみを実行しました。この1つの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
2
Bouke Woudstra

これはかなり古い投稿ですが、問題が発生し(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
2
aerostitch

ええと、私は基本的に同じ方法で本当に醜く恐ろしい方法で達成しましたが、それはより速かったです。 :)テストがアルファベット順に実行されていることに気づいたら:

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
Dylan

私はこの正確な問題に遭遇し、あなたが説明したことを正確に実行するためにTest::Unit::TestCaseのサブクラスを作成しました。

これが私が思いついたものです。 'test'で始まるクラス内のメソッドの数をカウントする独自のsetupおよびteardownメソッドを提供します。 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

これの欠点は、setup :method_nameクラスメソッド(Rails 2.X?)でのみ使用可能)を使用しない限り、独自のテストごとのsetupメソッドとteardownメソッドを提供できないことです。また、テストスイートまたはテストメソッドの1つのみを実行するものがある場合、すべてのテストメソッドが最終的に実行されることを前提としているため、global_teardownは呼び出されません。

1

@ orion-edwardsによる上記のRSpec回答の+1。私は彼の答えにコメントしただろうが、私はまだ答えにコメントするのに十分な評判を持っていない。

私はtest/unit and RSpecをよく使用しますが、言わなければなりません...誰もが投稿しているコードにverybefore(: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_メソッド中に作成された変数にアクセスするには、次のいずれかを行う必要があります。

  • rSpecのように、_#startup_によって作成されたすべてのインスタンス変数をコピーします
  • _#startup_の変数を、テストメソッドからアクセスできるスコープに定義します。 _@@class_variables_または、_@instance_variables_内で作成した_def self.startup_へのアクセスを提供するクラスレベルのattr_accessorsを作成します。

ちょうど私の0.02ドル!

0
remi

SetupOnceというミックスインを作成しました。使用例を示します。

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-taining-that-c​​lasses-name/

0
Michael A

各テストスイートの特別な準備について説明した@ romulo-a-cecconとしてTestSuiteを使用します。

ただし、ここで言及する必要があるのは、ユニットテストは完全に分離して実行することです。したがって、実行フローはsetup-test-teardownであり、各テストが他のテストの実行に影響されないことを保証する必要があります。

0
Honza