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()を作成することはできないようです。
それは可能ですか?それとも私はこれを絶望的に複雑にしていますか?
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
それが機能するはずです!
各テストは他のテストから完全に分離する必要があるため、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ドキュメント ドキュメントは、スイートがどのように機能するかについての良い説明を提供します。
最後に、テストユニットにはこれが実装されています! 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
この問題を解決するために、セットアップ構成を使用し、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
これはかなり古い投稿ですが、問題が発生し(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
ええと、私は基本的に同じ方法で本当に醜く恐ろしい方法で達成しましたが、それはより速かったです。 :)テストがアルファベット順に実行されていることに気づいたら:
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
それはきれいではありませんが、動作します:)
私はこの正確な問題に遭遇し、あなたが説明したことを正確に実行するために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
は呼び出されません。
@ 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
_メソッド中に作成された変数にアクセスするには、次のいずれかを行う必要があります。
#startup
_によって作成されたすべてのインスタンス変数をコピーします#startup
_の変数を、テストメソッドからアクセスできるスコープに定義します。 _@@class_variables
_または、_@instance_variables
_内で作成した_def self.startup
_へのアクセスを提供するクラスレベルのattr_accessorsを作成します。ちょうど私の0.02ドル!
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
脚注:
各テストスイートの特別な準備について説明した@ romulo-a-cecconとしてTestSuiteを使用します。
ただし、ここで言及する必要があるのは、ユニットテストは完全に分離して実行することです。したがって、実行フローはsetup-test-teardownであり、各テストが他のテストの実行に影響されないことを保証する必要があります。