28

私は Test::Unit に苦労しています。単体テストについて考えるとき、ファイルごとに 1 つの単純なテストを思い浮かべます。しかし、Ruby のフレームワークでは、代わりに次のように書かなければなりません。

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

   def test_1 
   end

   def test_1 
   end
end

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

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

4

10 に答える 10

28

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
于 2009-04-22T19:09:39.047 に答える
11

最後に、test-unit にこれが実装されました! ウット! 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

于 2012-09-25T14:53:54.697 に答える
10

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

各テストは残りの部分から完全に分離する必要があるため、メソッドsetuptear_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))

は、含まれる一連のテスト ケースの実行の前後に 1 回だけ実行されるandメソッドTestDecoratorを提供する特別なスイートを定義します。setuptear_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ドキュメントドキュメントは、スイートがどのように機能するかについての適切な説明を提供します。

于 2008-11-01T20:25:37.927 に答える
2

私はこれがかなり古い投稿であることを知っていますが、私は問題を抱えていて (そしてすでに 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
于 2013-09-17T12:53:25.200 に答える
2

この問題を解決するために、私は setup コンストラクトを使用しました。テスト メソッドは 1 つしかありません。この 1 つのテストメソッドが他のすべてのテストを呼び出しています。

例えば

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
于 2010-11-07T23:02:03.287 に答える
1

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

これが私が思いついたものです。「test」で始まるクラス内のメソッドの数をカウントする独自のメソッドを提供しますsetupteardownそれへの最初の呼び出し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

これの欠点は、クラス メソッド (Rails 2.X でのみ使用可能ですか?)を使用しない限り、テストごとに独自のメソッドsetupを提供できないことです。すべてのテストメソッドが最終的に実行されると想定しているため、 は呼び出されません。teardownsetup :method_nameglobal_teardown

于 2008-11-01T21:02:31.190 に答える
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-contain-that-c​​lasses-name/

于 2008-12-15T21:07:16.730 に答える
0

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

ただし、単体テストは完全に分離して実行するように構成されていることをここで言及する必要があると思います。したがって、実行フローは setup-test-teardown であり、各テストが他のテストの影響を受けずに実行されることを保証する必要があります。

于 2008-11-01T21:48:21.847 に答える
0

@orion-edwardsによる上記のRSpec回答の+1。私は彼の答えにコメントしたでしょうが、答えについてコメントするのに十分な評判はまだありません.

私は test/unitRSpec をよく使用しますが、言わなければならないのは... みんなが投稿しているコードには、@instance 変数のサポートという非常に重要な機能が欠けているということです。before(:all)

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これらのメソッドがクラス全体で 1 回だけ呼び出されるようにすることに重点を置いていTestCaseますが、これらのメソッドで使用されているインスタンス変数はすべて失われます。

RSpecbefore(:all)はオブジェクトの独自のインスタンスで実行され、すべてのローカル変数は各テストが実行される前にコピーされます。

グローバル メソッドで作成された変数にアクセスするには、次の#startupいずれかを行う必要があります。

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

わずか0.02ドル!

于 2011-08-13T17:10:19.750 に答える