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

Какова наилучшая практика, когда дело доходит до тестирования "бесконечных циклов"?

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

Возьмем этот код, например:

module Blah
  extend self

  def run
     some_initializer_method
     loop do
        some_other_method
        yet_another_method
     end
  end
end

Я хочу протестировать метод Blah.run с помощью Rspec (также я использую RR, но простой rspec будет приемлемым ответом).

Я считаю, что лучший способ сделать это - это разложить бит больше, например, отделить цикл от другого метода или что-то еще:

module Blah
  extend self

  def run
     some_initializer_method
     do_some_looping
  end

 def do_some_looping
   loop do
     some_other_method
     yet_another_method
   end
 end
end

... это позволяет нам протестировать run и высмеять цикл... но в какой-то момент необходимо проверить код внутри цикла.

Итак, что бы вы сделали в такой ситуации?

Просто не проверяйте эту логику, то есть тест some_other_method и yet_another_method, но не do_some_looping?

Перерыв петли в какой-то момент через макет?

... что-то еще?

4b9b3361

Ответ 1

Что может быть более практичным, так это выполнить цикл в отдельном потоке, утверждать, что все работает правильно, а затем завершать поток, когда он больше не требуется.

thread = Thread.new do
  Blah.run
end

assert_equal 0, Blah.foo

thread.kill

Ответ 2

Как насчет того, чтобы тело цикла было в отдельном методе, например calculateOneWorldIteration? Таким образом, вы можете вращать цикл в тесте по мере необходимости. И это не повредит API, его вполне естественный метод для публичного интерфейса.

Ответ 3

в rspec 3.3, добавьте эту строку

allow(subject).to receive(:loop).and_yield

до вашего крючка будет простой выход в блок без каких-либо циклов

Ответ 4

Вы не можете проверить что-то, что работает навсегда.

Когда вы сталкиваетесь с частью кода, который трудно (или невозможно) проверить, вы должны: -

  • Рефлятор для изоляции сложной тестовой части кода. Сделайте непроверенные части крошечными и тривиальными. Комментарий, чтобы они не были расширены, чтобы стать нетривиальными.
  • Unit test другие части, которые теперь отделены от труднодоступной секции
  • Трудная для тестирования часть будет покрыта интеграционным или приемочным тестом

Если основной цикл в вашей игре проходит только один раз, это будет сразу же очевидно при запуске.

Ответ 5

Как насчет насмешивания цикла, чтобы он выполнялся только количество раз, которое вы указали?

Module Object
    private
    def loop
        3.times { yield }
    end
end

Конечно, вы издеваетесь над этим только в своих спецификациях.

Ответ 6

Я знаю, что это немного старо, но вы также можете использовать метод yields для подделки блока и передать одну итерацию методу цикла. Это должно позволить вам протестировать методы, которые вы вызываете в своем цикле, не вставляя их в бесконечный цикл.

require 'test/unit'
require 'mocha'

class Something
  def test_method
    puts "test_method"
    loop do
      puts String.new("frederick")
    end
  end
end

class LoopTest < Test::Unit::TestCase

  def test_loop_yields
    something = Something.new
    something.expects(:loop).yields.with() do
      String.expects(:new).returns("samantha")
    end
    something.test_method
  end
end

# Started
# test_method
# samantha
# .
# Finished in 0.005 seconds.
#
# 1 tests, 2 assertions, 0 failures, 0 errors

Ответ 7

Я почти всегда использую конструкцию catch/throw для тестирования бесконечных циклов.

Возникновение ошибки также может работать, но это не идеально, особенно если ваш цикл цикла блокирует все ошибки, включая Исключения. Если ваш блок не освобождает Exception (или какой-либо другой класс ошибок), вы можете подклассифицировать Exception (или другой невосстановленный класс) и спасти свой подкласс:

Пример исключения

Настройка

class RspecLoopStop < Exception; end

Тест

blah.stub!(:some_initializer_method)
blah.should_receive(:some_other_method)
blah.should_receive(:yet_another_method)
# make sure it repeats
blah.should_receive(:some_other_method).and_raise RspecLoopStop

begin
  blah.run
rescue RspecLoopStop
  # all done
end

Пример Catch/throw:

blah.stub!(:some_initializer_method)
blah.should_receive(:some_other_method)
blah.should_receive(:yet_another_method)
blah.should_receive(:some_other_method).and_throw :rspec_loop_stop

catch :rspec_loop_stop
  blah.run
end

Когда я впервые попробовал это, я был обеспокоен тем, что использование should_receive во второй раз на :some_other_method будет "перезаписывать" первую, но это не так. Если вы хотите сами убедиться, добавьте блоки в should_receive, чтобы узнать, вызвало ли оно ожидаемое количество раз:

blah.should_receive(:some_other_method) { puts 'received some_other_method' }

Ответ 8

:) Несколько месяцев назад у меня был этот запрос.

Короткий ответ - нет простого способа проверить это. Вы проверяете внутренность петли. Затем вы примените метод петли и выполните ручной тест, чтобы цикл работал до завершения условия завершения.

Ответ 9

Самое легкое решение, которое я нашел, - это дать цикл один раз и вернуть. Я использовал мокко здесь.

require 'spec_helper'
require 'blah'

describe Blah do
  it 'loops' do
    Blah.stubs(:some_initializer_method)
    Blah.stubs(:some_other_method)
    Blah.stubs(:yet_another_method)

    Blah.expects(:loop).yields().then().returns()

    Blah.run
  end
end

Мы ожидаем, что цикл будет фактически выполнен, и он обеспечит его выход после одной итерации.

Тем не менее, как указано выше, хорошей практикой является сохранение метода петли как можно более малым и глупым.

Надеюсь, это поможет!

Ответ 10

Наше решение для тестирования цикла, которое выходит только из сигналов, заключалось в том, чтобы заглушить метод условия выхода, чтобы вернуть false первый раз, но true второй раз, гарантируя, что цикл выполняется только один раз.

Класс с бесконечным циклом:

class Scheduling::Daemon
  def run
    loop do
      if daemon_received_stop_signal?
        break
      end

      # do stuff
    end
  end
end

spec, проверяющий поведение цикла:

describe Scheduling::Daemon do
  describe "#run" do
    before do
      Scheduling::Daemon.should_receive(:daemon_received_stop_signal?).
        and_return(false, true)  # execute loop once then exit
    end      

    it "does stuff" do
      Scheduling::Daemon.run  
      # assert stuff was done
    end
  end
end