我的基本逻辑是在某处运行无限循环并尽可能地测试它。拥有无限循环的原因并不重要(游戏的主循环,类似守护进程的逻辑......)而且我更多地询问有关这种情况的最佳实践。
我们以此代码为例:
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
?
通过模拟在某个时刻让循环中断吗?
......别的什么?
答案 0 :(得分:15)
更实际的是在单独的线程中执行循环,声明一切正常,然后在不再需要时终止线程。
thread = Thread.new do
Blah.run
end
assert_equal 0, Blah.foo
thread.kill
答案 1 :(得分:10)
如何在单独的方法中使用循环体,如calculateOneWorldIteration
?这样你就可以根据需要在测试中旋转循环。它并没有损害API,它是一种非常自然的公共界面方法。
答案 2 :(得分:9)
在rspec 3.3中,添加此行
allow(subject).to receive(:loop).and_yield
到你的前钩子将简单地屈服于块而没有任何循环
答案 3 :(得分:3)
你无法测试那些永远存在的东西。
当面对难以(或不可能)测试的一段代码时,您应该: -
如果你游戏中的主循环只进行一次,那么当你运行它时会立即显现出来。
答案 4 :(得分:2)
如何模拟循环以便只执行指定的次数?
Module Object
private
def loop
3.times { yield }
end
end
当然,你只能在你的规格中嘲笑它。
答案 5 :(得分:2)
我知道这有点旧,但你也可以使用yield方法伪造一个块并将一个迭代传递给一个循环方法。这应该允许您测试在循环中调用的方法,而不是实际将它放入无限循环中。
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
答案 6 :(得分: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
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' }
答案 7 :(得分:0)
:)几个月前我有this query。
简短的回答是没有简单的方法来测试它。您测试驱动循环的内部。然后你把它变成一个循环方法&amp;做一个循环工作直到终止条件的手动测试。
答案 8 :(得分:0)
我找到的最简单的解决方案是产生循环一次而不是返回。我在这里使用过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
我们期待循环实际执行,并确保它在一次迭代后退出。
尽管如上所述,保持循环方法尽可能小而愚蠢是一种很好的做法。
希望这有帮助!
答案 9 :(得分:0)
我们测试仅在信号上退出的循环的解决方案是将退出条件方法存根,以便第一次返回false,但是第二次返回true,确保循环仅执行一次。
具有无限循环的类:
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