Какова наилучшая практика при тестировании «бесконечных циклов»? - PullRequest
24 голосов
/ 19 апреля 2011

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

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

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?

Есть ли разрыв цикла в какой-то момент через макет?

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

Ответы [ 10 ]

15 голосов
/ 19 апреля 2011

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

thread = Thread.new do
  Blah.run
end

assert_equal 0, Blah.foo

thread.kill
10 голосов
/ 19 апреля 2011

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

9 голосов
/ 26 октября 2015

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

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

в ваш перед хуком простой выход в блок без зацикливания

3 голосов
/ 09 апреля 2012

Нельзя проверять то, что работает вечно.

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

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

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

2 голосов
/ 09 апреля 2012

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

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

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

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

1 голос
/ 13 сентября 2012

Я почти всегда использую конструкцию 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

Пример улова / броска:

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' }
0 голосов
/ 05 марта 2014

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

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

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
0 голосов
/ 22 декабря 2012

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

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

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

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

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

0 голосов
/ 09 апреля 2012

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

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

...