26

私の基本的なロジックは、無限ループをどこかで実行し、可能な限りテストすることです。無限ループの理由は重要ではありません (ゲームのメイン ループ、デーモンのようなロジックなど)、そのような状況に関するベスト プラクティスについて質問しています。

たとえば、次のコードを見てみましょう。

module Blah
  extend self

  def run
     some_initializer_method
     loop do
        some_other_method
        yet_another_method
     end
  end
end

Rspec を使用してメソッドをテストしたいBlah.run(また、 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?を意味します。

モックを介してある時点でループを中断しましたか?

…他に何か?

4

10 に答える 10

15

より実用的な方法は、別のスレッドでループを実行し、すべてが正しく機能していることを確認してから、スレッドが不要になった時点でスレッドを終了することです。

thread = Thread.new do
  Blah.run
end

assert_equal 0, Blah.foo

thread.kill
于 2011-04-19T14:25:41.697 に答える
12

rspec 3.3 では、この行を追加します

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

あなたの前のフックにループすることなく、単純にブロックに譲ります

于 2015-10-26T14:40:22.553 に答える
10

のように、ループの本体を別のメソッドに入れるのはcalculateOneWorldIterationどうですか? そうすれば、必要に応じてテストでループを回すことができます。また、API に悪影響を与えることはありません。パブリック インターフェイスで使用するのは非常に自然な方法です。

于 2011-04-19T14:25:48.583 に答える
3

永久に実行されるものをテストすることはできません。

テストが困難な (または不可能な) コードのセクションに直面した場合は、次のことを行う必要があります。

  • コードのテストが難しい部分を分離するためにリファクタリングします。テストできない部分を小さくて些細なものにします。後で拡張されて重要なものにならないようにコメントする
  • テストが難しいセクションから分離された他の部分の単体テスト
  • テストが難しい部分は、統合または受け入れテストでカバーされます

ゲームのメイン ループが 1 回しか回らない場合は、実行するとすぐにわかります。

于 2012-04-09T00:47:50.710 に答える
2

指定した回数だけ実行されるようにループをモックするのはどうですか?

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

もちろん、これをモックするのは仕様内だけです。

于 2011-04-19T14:46:02.023 に答える
2

これが少し古いことは承知していますが、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
于 2012-04-09T00:31:32.507 に答える
1

シグナルでのみ終了するループをテストするための解決策は、終了条件メソッドをスタブして、最初は false を返し、2 回目は true を返し、ループが 1 回だけ実行されるようにすることでした。

無限ループのあるクラス:

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

      # do stuff
    end
  end
end

ループの動作をテストする仕様:

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
于 2014-03-05T02:39:54.607 に答える
1

私はほとんどの場合、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最初にこれを試したとき、2回目に使用する:some_other_methodと最初のものを「上書き」するのではないかと心配しましたが、そうではありません. 自分で確認したい場合は、ブロックを追加してshould_receive、予想される回数呼び出されるかどうかを確認します。

blah.should_receive(:some_other_method) { puts 'received some_other_method' }
于 2012-09-13T16:04:36.740 に答える
0

:)私は数ヶ月前にこのクエリを持っていました。

簡単な答えは、それをテストする簡単な方法はないということです。ループの内部をテストドライブします。次に、それをループメソッドに入れて、終了条件が発生するまでループが機能することを手動でテストします。

于 2012-04-09T12:41:57.343 に答える
0

私が見つけた最も簡単な解決策は、ループを 1 回生成してから戻ることです。ここではモカを使っています。

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

ループが実際に実行され、1 回の反復後に終了することが保証されていることを期待しています。

それにもかかわらず、上で述べたように、ループ メソッドをできるだけ小さく、ばかげたものにしておくことをお勧めします。

お役に立てれば!

于 2012-12-22T14:07:34.193 に答える