我是Ruby和RSpec的新手,已经花了好几个小时尝试编写测试交互式tic tac toe program的第一步,但是没有找到任何有用的答案来解决我得到的错误:
> bundle exec rspec --format documentation
Do you want to play tic-tac-toe? (y/n)
An error occurred while loading ./spec/tic_tac_toe_spec.rb.
Failure/Error: choice = gets.chomp.downcase
Errno::ENOENT:
No such file or directory @ rb_sysopen - --format
# ./lib/tic_tac_toe.rb:70:in `gets'
# ./lib/tic_tac_toe.rb:70:in `gets'
# ./lib/tic_tac_toe.rb:70:in `start_game'
# ./lib/tic_tac_toe.rb:144:in `<top (required)>'
# ./spec/tic_tac_toe_spec.rb:1:in `require'
# ./spec/tic_tac_toe_spec.rb:1:in `<top (required)>'
No examples found.
Finished in 0.0004 seconds (files took 0.08415 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
当您开始游戏时,它会提示您是否要播放(是/否)。我已经尝试过使用模拟和存根的不同变体但总是会得到同样的错误。我想我错过了一些非常基本但却看不到的东西。
问题:如何充分解决(使用RSpec)游戏的第一个提示?
游戏使用独立的方法(start_game
)但不是一个类来启动。我正在寻找某种方式模拟或存储一些默认输入(如y
或n
),因为我希望测试自动化。我看到的所有示例都使用类来启动程序(而不是像这里的独立方法)。
目前在spec/tic_tac_toe_spec.rb
let
:output
有:game
个choice = gets.chomp.downcase
个关键字,我在某个地方找到了一个示例,但显然这不起作用。
修改我想测试以下方法。 RSpec代码在def start_game
puts "Do you want to play tic-tac-toe? (y/n)"
choice = gets.chomp.downcase
unless ["y","n"].include?(choice)
abort("Please answer with y or n.")
end
case choice
when "y"
puts "Ok, let's start!"
b = Board.new
puts "Enter name of player 1 (O)"
player1name = gets.chomp
player1 = Player.new(player1name)
puts "Now enter name of player 2 (X)"
player2name = gets.chomp
player2 = Player.new(player2name)
play_game(player1, player2, b)
when "n"
puts "Your loss."
end
end #start_game
行上保持窒息。
import numpy as np
training_set = np.array([[0, 1, 0, 1, 0, 1],[0, 0, 0, 1, 0, 0],[0, 0, 0, 0, 1, 0],[1, 0, 1, 0, 1, 0],[0, 1, 1, 1, 0, 1],[0, 1, 0, 0, 1, 1],[1, 1, 1, 0, 0, 0],[1, 1, 1, 1, 0, 1],[0, 1, 1, 0, 1, 0],[1, 1, 0, 0, 0, 1],[1, 0, 0, 0, 1, 0]])
def p(X):
Fx = X[:,X.shape[1]-1]
x0= 0
x1= 0
for i in range(len(Fx)):
if Fx[i-1] == 1:
x0 = x0+1
else:
x1 = x1+1
P0 = x0/len(Fx)
P1 = x1/len(Fx)
return(P0,P1)
def H(X):
result = -p(X)[0]*np.log(p(X)[0])-p(X)[1]*np.log(p(X)[1]) #needs to be log2
print("1 = pure, 0 = unpure 1/2 = decision can be random: Calculating Entropy: -" + str(p(X)[0]) + "*" + str(np.log(p(X)[0])) + "-" + str(p(X)[1]) + "*" + str(np.log(p(X)[1])) )
return result
def Q(X,i):
Xi = X[:,i]
result0= 0
result1= 0
for j in range(len(Xi)):
if Xi[j] == 1:
result1 = result1 + len(X[i,:])
else: result0 = result0 + len(X[i,:])
result1 = result1/len(X)
result0 = result0/len(X)
return(result0,result1)
def X_column(X,i,v):
list = X[np.where(X[:,i] == v)]
return list
def IG(X,i):
result = H(X)-Q(X,i)[0]*H(X_column(X,i,0))-Q(X,i)[1]*H(X_column(X,i,1))
return result
#To teach decision trees on learning set S, we will used following alorithm(ID3):
# 1. There is example set S
# 2. If |{f(x) : (x, f(x)) ∈ S}| = 1= 1 create leaf with label f(x)
# 3. For i = 1,2,...,n calculate value IG(S,i)
# 4. May j be an index o fthe biggest of calculated values
# 5. Set node with label Xj
# 6. For subsets:
# S0 = {(x, f(x)) ∈ S : xj = 0}
# and
# S1 = {(x, f(x)) ∈ S : xj = 1}
# run algorithm recurrent (for S ← S0 i S ← S1) and add new nodes as a childs for a node with label j
def ID3(S, recursion = 0, label = 0, tree = np.array()):
result = np.array()
recursion += 1
rows = S.shape[0]
columns = S.shape[1]
if S[:,columns-1] == True:
tree[recursion]= S[0,columns-1]
break
for i in range(rows):
result[i]= IG(S,i)
j = result.max()
tree[recursion]= 1 #czym jest etykieta xj
S0 = X_column(S,i,0)
tree[recursion+1] = ID3(S0,recursion = recursion )
S1 = X_column(S,i,1)
tree[recursion+2] = ID3(S1,recursion = recursion)
return tree
def pruning():
return tree
答案 0 :(得分:2)
您收到错误是因为您将--format documentation
作为参数传递给RSpec。
gets
读取已通过的ARGV。这还包括RSpec参数。您应该使用STDIN.gets
,这样您才能阅读标准输入而不是参数。
您可以在此问题中详细了解: https://stackoverflow.com/a/19799223/6156030
答案 1 :(得分:1)
一个简单的方法可能是:
it 'works' do
allow($stdin).to receive(:gets).and_return('y')
expect { start_game } # (Or however you run it!)
.to output("Ok, let's start!")
.to_stdout
end
您还可以为gets
指定多个返回值,这些值将在测试运行时按顺序使用。
你似乎已经开始(但没有完全实现)的另一种方法是在游戏中注入一个明确的output
(可能是一天input
)对象。
从纯粹主义者的角度来看,这确实是一种更清晰的方法 - 即不会像$stdin
那样存在全局对象 - 但对于您的初始版本的代码来说可能有点过分。除非你打算做一些像运行并行规范这样的事情,否则我不会为此担心。
编辑:在更详细地查看您的实际代码之后,我看到了问题。您正在定义全局方法,这些方法正在执行多项操作且紧密耦合。这使得编写测试更加困难!
这里我添加了一个工作测试示例:
https://github.com/tom-lord/tic_tac_toe_rspec/commit/840df0b7f1380296db97feff0cd3ca995c5c6ee3
然而,为了简化这一点,我的建议是在适当的类中定义所有每个方法,并使代码更少程序化。 (即,只是让一个方法的结束以长序列调用下一个方法!)这种重构可能超出了StackOverflow答案的范围。
答案 2 :(得分:0)
您需要在规范中使用stub and mock gets
方法:
yes_gets = double("yes_gets")
allow($stdin).to receive(:gets).and_return(yes_gets)
然后您可以回复#chomp
:
expect(yes_gets).to receive(:chomp).and_return('Y')
您可以通过返回此双重对象来覆盖downcase
的类似方法调用。
您也可以为'N'
情况做模拟对象的类似工作,当玩家输入N(否)时,您希望游戏退出:
no_gets = double("no_gets")
allow($stdin).to receive(:gets).and_return(no_gets)
expect(no_gets).to receive(:chomp).and_return('N')