В Ruby Test:: Unit:: TestCase, как мне переопределить метод initialize?
Я борюсь с Test:: Unit. Когда я думаю об модульных тестах, я думаю об одном простом испытании для каждого файла. Но в Ruby framework я должен написать:
class MyTest < Test::Unit::TestCase
def setup
end
def test_1
end
def test_1
end
end
Запуск установки и пробоя при каждом вызове метода test_ *. Это именно то, чего я не хочу. Скорее, я хочу, чтобы метод установки выполнялся только один раз для всего класса. Но я не могу написать свой собственный initialize() без нарушения инициализации TestCase.
Это возможно? Или я делаю это безнадежно сложным?
Ответы
Ответ 1
Как упоминалось в книге Хэла Фултона "Рубиновый путь".
Он переопределяет self.suite метод Test:: Unit, который позволяет тестовым примерам в классе работать как набор.
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
Ответ 2
Как он должен работать!
Каждый тест должен быть полностью изолирован от остальных, поэтому методы setup
и tear_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
определяет специальный набор, который предоставляет методы setup
и tear_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 документации дает хорошее объяснение того, как работают пакеты.
Ответ 3
НАКОНЕЦ, тестовое устройство реализовано! Woot!
Если вы используете 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
Ответ 4
Хорошо, я сделал в основном то же самое в действительно уродливой и ужасной манере, но это было быстрее.:) Как только я понял, что тесты выполняются в алфавитном порядке:
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
Это неплохо, но он работает:)
Ответ 5
Чтобы решить эту проблему, я использовал конструкцию установки, в которой использовался только один метод тестирования. Этот один тестовый метод вызывает все другие тесты.
Например
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
Ответ 6
Я знаю, что это довольно старая статья, но у меня была проблема (и я уже писал классы с использованием Tes/unit), а 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
Ответ 7
Я столкнулся с этой точной проблемой и создал подкласс Test::Unit::TestCase
для выполнения именно того, что вы описали.
Вот что я придумал. Он предоставляет собственные методы setup
и teardown
, которые подсчитывают количество методов в классе, которые начинаются с '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
Ошибка в этом состоит в том, что вы не можете предоставить свои собственные методы для каждого теста setup
и teardown
, если вы не используете метод класса setup :method_name
(доступен только в Rails 2.X?), и если у вас есть набор тестов или что-то, что только запускает один из методов тестирования, тогда global_teardown
не будет вызываться, потому что он предполагает, что все тестовые методы будут запущены в конечном итоге.
Ответ 8
Используйте TestSuite как @romulo-a-ceccon, описанный для специальных приготовлений для каждого набора тестов.
Однако я думаю, что здесь следует упомянуть, что тесты Unit предназначены для полной изоляции. Таким образом, поток выполнения - это установка-тестирование-отключение, которое должно гарантировать, что каждый тестовый прогон невозможен ничем иным, чем другие тесты.
Ответ 9
Я создал mixin с именем 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
Сноска:
Ответ 10
+1 для ответа RSpec выше на @orion-edwards. Я бы прокомментировал его ответ, но у меня пока нет достаточной репутации, чтобы комментировать ответы.
Я использую test/unit и 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 scope before each test runs.
it 'second' do
assert_equal 'foo', @foo
@foo = 'different'
assert_equal 'different', @foo
end
end
Реализации #startup
и #shutdown
прежде всего направлены на то, чтобы эти методы вызывались только один раз для всего класса TestCase
, но любые переменные экземпляра, используемые в этих методах, будут потеряны!
RSpec запускает свой before(:all)
в своем собственном экземпляре Object и все локальные переменные копируются до запуска каждого теста.
Чтобы получить доступ ко всем переменным, созданным во время глобального метода #startup
, вам необходимо либо:
- скопируйте все переменные экземпляра, созданные
#startup
, например RSpec
- определите ваши переменные в
#startup
в области, доступ к которой вы можете получить из своих методов тестирования, например. @@class_variables
или создать attr_accessors на уровне класса, обеспечивающие доступ к @instance_variables
, который вы создаете внутри def self.startup
Просто мои $0,02!