В Ruby's Test :: Unit :: TestCase как переопределить метод инициализации? - PullRequest
26 голосов
/ 01 ноября 2008

Я борюсь с Test :: Unit. Когда я думаю о модульных тестах, я думаю об одном простом тесте на файл. Но в рамках Ruby я должен написать:

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

   def test_1 
   end

   def test_1 
   end
end

Но настройка и разборка выполняются для каждого вызова метода test_ *. Это именно то, чего я не хочу. Скорее, я хочу метод установки, который запускается только один раз для всего класса. Но я не могу написать свою собственную initialize () без нарушения инициализации TestCase.

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

Ответы [ 10 ]

25 голосов
/ 22 апреля 2009

Как упоминалось в книге Хэла Фултона "Рубиновый путь". Он переопределяет метод 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
9 голосов
/ 01 ноября 2008

Вот как это должно работать!

Каждый тест должен быть полностью изолирован от остальных, поэтому методы setup и tear_down выполняются один раз для каждого теста. Однако существуют случаи, когда вам может потребоваться больший контроль над потоком выполнения. Затем вы можете сгруппировать тестовые наборы в suite .

В вашем случае вы могли бы написать что-то вроде следующего:

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 предоставляет хорошее объяснение того, как работают комплекты.

7 голосов
/ 25 сентября 2012

НАКОНЕЦ, тестовый модуль это реализовал! Woot! Если вы используете v 2.5.2 или новее, вы можете просто использовать это:

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

Это запустится один раз, когда вы начнете свои тесты. Есть также обратные вызовы, которые выполняются в начале каждого теста (запуска), в дополнение к тем, которые выполняются перед каждым тестом (настройкой).

http://test -unit.rubyforge.org / тест-блок / ен / Test / Unit.html # at_start-class_method

2 голосов
/ 17 сентября 2013

Я знаю, что это довольно старый пост, но у меня была проблема (и я уже писал классы с использованием 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 голосов
/ 08 ноября 2010

Чтобы решить эту проблему, я использовал конструкцию setup, следуя только одному методу тестирования. Этот метод test вызывает все остальные тесты.

Например

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 голосов
/ 14 июля 2009

Ну, я сделал в основном то же самое, действительно ужасно и ужасно, но это было быстрее :) Однажды я понял, что тесты запускаются в алфавитном порядке:

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

Это не красиво, но работает :) 1004 *

1 голос
/ 02 ноября 2008

Я столкнулся с этой проблемой и создал подкласс 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 не будет вызван, поскольку предполагается, что все методы тестирования будут запущены в конечном итоге.

0 голосов
/ 13 августа 2011

+ 1 для ответа RSpec выше @ orion-edwards. Я бы прокомментировал его ответ, но у меня пока недостаточно репутации, чтобы комментировать ответы.

Я часто использую test / unit и RSpec, и должен сказать ... код, который публикуется всеми, отсутствует очень важная функция before(:all), которая is: поддержка переменных @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 запускает before(:all) в своем собственном экземпляре Object, и все локальные переменные копируются перед каждым тестом.

Чтобы получить доступ к любым переменным, которые создаются во время глобального метода #startup, вам необходимо:

  • копировать все переменные экземпляра, созданные #startup, как делает RSpec
  • определите ваши переменные в #startup в области, к которой вы можете получить доступ из ваших методов тестирования, например. @@class_variables или создайте attr_accessors уровня класса, которые предоставляют доступ к @instance_variables, который вы создаете внутри def self.startup

Только мои 0,02 доллара!

0 голосов
/ 16 декабря 2008

Я создал миксин под названием 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-containing-that-classes-name/

0 голосов
/ 02 ноября 2008

Используйте TestSuite как @ romulo-a-ceccon, описанный для специальных приготовлений для каждого набора тестов.

Однако я думаю, что здесь следует упомянуть, что модульные тесты должны выполняться в полной изоляции. Таким образом, потоком выполнения является установка-тест-разрыв, который должен гарантировать, что каждый запуск теста не нарушен чем-то, что делали другие тесты.

...