Подтвердить что ты не робот

В 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.

Это возможно? Или я делаю это безнадежно сложным?

4b9b3361

Ответ 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!