Ruby - Testing a method that calls itself in Minitest - ruby

I'm having trouble developing unit tests for a method that calls itself (a game loop) in Ruby using minitest. What I've attempted has been stubbing the method I'm trying to call in said game loop with my input. Here's the game loop:
#main game loop
def playRound
#draw board
#board.printBoard
#get input
playerInput = gets.chomp #returns user input without ending newline
#interpret input, quitting or beginning set selection for a player
case playerInput
when "q"
quit
when "a", "l"
set = getPlayerSet()
if(playerInput == "a")
player = 1
else
player = 2
end
when "h"
if #hintsEnabled
giveHint
playRound
else
puts "Hints are disabled"
playRound
end
else
puts "Input not recognized."
end
if(set != nil)
#have board test set
checkSet(set, player)
end
#check if player has quitted or there are no more valid sets
unless #quitted || #board.boardComplete
playRound
end
end
Much of it is ultimately irrelevant, all I'm trying to test is that this switch statement is calling the correct methods. Currently I'm trying to circumvent the loop by stubbing the called method to raise an error (which my test assers_raise's):
def test_playRound_a_input_triggers_getPlayerSet
#game.stub :getPlayerSet, raise(StandardError) do
assert_raises(StandardError) do
simulate_stdin("") {
#game.playRound
}
end
end
end
This approach does not seem to work, however, as Minitest is recording the results of the above test as an error with the message
E
Error:
TestGame#test_playRound_a_input_triggers_getPlayerSet:
StandardError: StandardError
test_game.rb:136:in `test_playRound_a_input_triggers_getPlayerSet'
If anyone has any advice or direction for me it would be massively appreciated as I can't tell what's going wrong

I'm not very familiar with minitest, but I expect you need to wrap the raise(exception) in a block, otherwise your test code is raising the exception immediately in your test (not as a result of the stubbed method being called).
Something like:
class CustomTestError < RuntimeError; end
def test_playRound_a_input_triggers_getPlayerSet
raise_error = -> { raise(CustomTestError) }
#game.stub(:getPlayerSet, raise_error) do
assert_raises(CustomTestError) do
simulate_stdin("") {
#game.playRound
}
end
end
end
-- EDIT --
Sometimes when i'm having difficulty testing a method it's a sign that I should refactor things to be easier to test (and thus have a cleaner, simpler interface, possibly be easier to understand later).
I don't code games and don't know what's typical for a game loop, but that method looks very difficult to test. I'd try to break it into a couple steps where each step/command can be easily tested in isolation. One option for this would be to define a method for each command and use send. This would allow you to test that each command works separately from your input parsing and separately from the game loop itself.
COMMANDS = {
q: :quit,
# etc..
}.stringify_keys.freeze
def play_round # Ruby methods should be snake_case rather than camelCase
#board.print_board
run_command(gets.chomp)
play_round unless #quitted || #board.board_complete
end
def run_command(input)
command = parse_input_to_command(input)
run_command(command)
end
def parse_input_to_command(input)
COMMANDS[input] || :bad_command
end
def run_command(command)
send("run_#{command}")
end
# Then a method for each command, e.g.
def run_bad_input
puts "Input not recognized"
end
However, for this type of problem I really like a functional approach, where each command is just a stateless function that you pass state into and get new state back. These could either mutate their input state (eww) or return a new copy of the board with updated state (yay!). Something like:
COMMANDS = {
# All state change must be done on board. To be a functional pattern, you should not mutate the board but return a new one. For this I invent a `.copy()` method that takes attributes to update as input.
q: -> {|board| board.copy(quitted: true) },
h: -> HintGiver.new, # If these commands were complex, they could live in a separate class entirely.
bad_command: -> {|board| puts "Unrecognized command"; board },
#
}.stringify_keys.freeze
def play_round
#board.print_board
command = parse_input_to_command(gets.chomp)
#board = command.call(#board)
play_round unless #board.quitted || #board.board_complete
end
def parse_input_to_command(input)
COMMANDS[input] || COMMANDS[:bad_command]
end

Related

Testing STDIN in Ruby

I'm currently trying to test a basic method that receives some input from the user (gets) and outputs it (puts). After a bit of research I found a good way to test the standard output stream which is the below:
def capture_standard_output(&block)
original_stream = $stdout
$stdout = mock = StringIO.new
yield
mock.string.chomp
ensure
$stdout = original_stream
end
The method I'm testing is the one below, output and input refer to ivars which I initialize in the beginning and point to the equivalent $stdout & $stdin:
def ask_for_mark
ouput.puts 'What shall I call you today?'
answer = input.gets.chomp.capitalize
answer
end
Now I've seen some solutions for STDIN but haven't really understood any of them and I definitely don't want to copy & paste. The only one I got to "work" is the one below, but it's not really working since when I run rspec it pauses and waits for input and by simply pressing enter, it passes:
it "takes user's name and returns it" do
output = capture_standard_output { game.ask_for_name }
expect(output).to eq "What shall I call you today?"
game.input.stub(:gets) { 'joe' }
expect(game.ask_for_name).to eq 'Joe'
end
What would be a good way to test STDIN? I've been staring at a screen for most of the day (not good, I know) so a fresh perspective and some help would be greatly appreciated :-)
Update
For anyone facing similar issues, I followed different (simpler) approaches. First, I would separate the IO operations in their own methods.
In the case of input, in the tests it can be pre-populated with the desired data so when the gets message is send, it will return that data which are separated by the newline character \n like so:
input = StringIO.new("one\ntwo\n")
=> #<StringIO:0x007f88152f3510>
input.gets
=> "one\n"
input.gets
=> "two\n"
input.gets
=> nil
This helps with keep the internals of the method under test, private without coupling the tests to the implementation details.
Another approach is to simply use polymorphism and pass in a Fake or a Spy object that conforms to the same api but instead of making a call to stdin or stdout, it returns canned data instead or in the case of the Spy, it registers the call.
class SpyIO
def initialize
was_called? = false
end
...
def ask_for_name
# call to stdout would normally take place
was_called? = true
end
...
end
class FakeIO
def initialize(data = [some, data])
#data = data
end
def get_user_input
# call to stdin would normally happen
#data.shift
end
...
end
There's tradeoffs in each approach but I thought I'd put these here in case anyone was having similar issues or considering options.
You can simply stub STDIN:
it "takes user's name and returns it" do
output = capture_standard_output { game.ask_for_name }
expect(output).to eq "What shall I call you today?"
allow(STDIN).to receive(:gets) { 'joe' }
expect(game.ask_for_name).to eq 'Joe'
end
Actually, you can do the same with STDOUT, without needing to change $stdout:
it "takes user's name and returns it" do
expect(STDOUT).to receive(:puts).with("What shall I call you today?")
allow(STDIN).to receive(:gets) { 'joe' }
expect(game.ask_for_name).to eq 'Joe'
end
Well I can't really be sure, but one problem I had (still have) when working with $stdin / $stdout / StringIO is making sure to run #rewind on them. Are you doing that?
input = StringIO.new
input.puts "joe"
input.rewind
input.gets #=> "joe\n"

Is it possible for RSpec to expect change in two tables?

RSpec expect change:
it "should increment the count" do
expect{Foo.bar}.to change{Counter.count}.by 1
end
Is there a way to expect change in two tables?
expect{Foo.bar}.to change{Counter.count}.by 1
and change{AnotherCounter.count}.by 1
I prefer this syntax (rspec 3 or later):
it "should increment the counters" do
expect { Foo.bar }.to change { Counter, :count }.by(1).and \
change { AnotherCounter, :count }.by(1)
end
Yes, this are two assertions in one place, but because the block is executed just one time, it can speedup the tests.
EDIT: Added Backslash after the .and to avoid syntax error
I got syntax errors trying to use #MichaelJohnston's solution; this is the form that finally worked for me:
it "should increment the counters" do
expect { Foo.bar }.to change { Counter.count }.by(1)
.and change { AnotherCounter.count }.by(1)
end
I should mention I'm using ruby 2.2.2p95 - I don't know if this version has some subtle change in parsing that causes me to get errors, it doesn't appear that anyone else in this thread has had that problem.
This should be two tests. RSpec best practices call for one assertion per test.
describe "#bar" do
subject { lambda { Foo.bar } }
it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end
If you don't want to use the shorthand/context based approach suggested earlier, you can also do something like this but be warned it will run the expectation twice so it might not be appropriate for all tests.
it "should increment the count" do
expectation = expect { Foo.bar }
expectation.to change { Counter.count }.by 1
expectation.to change { AnotherCounter.count }.by 1
end
Georg Ladermann's syntax is nicer but it doesn't work. The way to test for multiple value changes is by combining the values in arrays. Else, only the last change assertion will decide on the test.
Here is how I do it:
it "should increment the counters" do
expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end
This works perfectecly with the '.to' function.
The best way I've found is to do it "manually":
counters_before = Counter.count
another_counters_before = AnotherCounter.count
Foo.bar
expect(Counter.count).to eq (counters_before + 1)
expect(AnotherCounter.count).to eq (another_counters_before + 1)
Not the most elegant solution but it works
After none of the proposed solutions proved to actually work, I accomplished this by adding a change_multiple matcher. This will only work for RSpec 3, and not 2.*
module RSpec
module Matchers
def change_multiple(receiver=nil, message=nil, &block)
BuiltIn::ChangeMultiple.new(receiver, message, &block)
end
alias_matcher :a_block_changing_multiple, :change_multiple
alias_matcher :changing_multiple, :change_multiple
module BuiltIn
class ChangeMultiple < Change
private
def initialize(receiver=nil, message=nil, &block)
#change_details = ChangeMultipleDetails.new(receiver, message, &block)
end
end
class ChangeMultipleDetails < ChangeDetails
def actual_delta
#actual_after = [#actual_after].flatten
#actual_before = [#actual_before].flatten
#actual_after.map.with_index{|v, i| v - #actual_before[i]}
end
end
end
end
end
example of usage:
it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do
a = "." * 4
b = "." * 10
times_called = 0
expect {
times_called += 1
a += ".."
b += "-----"
}.to change_multiple{[a.length, b.length]}.by([2,5])
expect(times_called).to eq(1)
end
Making by_at_least and by_at_most work for change_multiple would require some additional work.
I'm ignoring the best practices for two reasons:
A set of my tests are regression tests, I want them to run fast, and
they break rarely. The advantage of having clarity about exactly
what is breaking isn't huge, and the slowdown of refactoring my code
so that it runs the same event multiple times is material to me.
I'm a bit lazy sometimes, and it's easier to not do that refactor
The way I'm doing this (when I need to do so) is to rely on the fact that my database starts empty, so I could then write:
foo.bar
expect(Counter.count).to eq(1)
expect(Anothercounter.count).to eq(1)
In some cases my database isn't empty, but I either know the before count, or I can explicitly test for the before count:
counter_before = Counter.count
another_counter_before = Anothercounter.count
foo.bar
expect(Counter.count - counter_before).to eq(1)
expect(Anothercounter.count - another_counter_before).to eq(1)
Finally, if you have a lot of objects to check (I sometimes do) you can do this as:
before_counts = {}
[Counter, Anothercounter].each do |classname|
before_counts[classname.name] = classname.count
end
foo.bar
[Counter, Anothercounter].each do |classname|
expect(classname.count - before_counts[classname.name]).to be > 0
end
If you have similar needs to me this will work, my only advice would be to do this with your eyes open - the other solutions proposed are more elegant but just have a couple of downsides in certain circumstances.

How do I properly override instance methods in Ruby?

I've been learning Ruby for a class and have been writing a sample game. Here is a bit of one of my classes:
class Player
def askIfTake
puts("Would you like to take a card? > ")
input = gets.chomp
input.downcase!
if input == "y" or input == "yes"
return 1
elsif input == "n" or input == "no"
return 0
else
puts("Invalid input. Please type y or n.")
return askIfTake
end
end
end
I then have another class:
class PlayerAI < Player
def initialize
super
end
def askIfTake
puts("this is an AI")
return rand(2)
end
end
The problem is, when I create an instance of PlayerAI, and attempt to call askIfTake from that instance, it calls the method declared in the Player class. Why is this happening?
This is not possible. I tested it (knowing it would be fine) and it worked for me:
>> PlayerAI.new
=> #<PlayerAI:0x00000103889308>
>> PlayerAI.new.askIfTake
this is an AI
You are doing this in the correct way. Check if you have any spelling mistakes. Incidentally, in Ruby the method usually uses underscores: PlayerAI.new.ask_if_take. Or better, with a question mark: PlayerAI.new.will_take?.

Ruby metaprogramming, how does RSpec's 'should' work?

I was reading up on RSpec and I was trying to figure out how RSpec's "should" was implemented.
Could someone give a hand on how the meta nature of this function works?
The code is located here:
http://github.com/dchelimsky/rspec/blob/master/lib/spec/expectations/extensions/kernel.rb
TIA,
-daniel
Clarification:
target.should == 5
How did target's value get passed along to "should", which in turn was "=="'d against 5?
Take a look at class OperatorMatcher.
It all boils down to Ruby allowing you to leave out periods and parenthesis. What you are really writing is:
target.should.send(:==, 5)
That is, send the message should to the object target, then send the message == to whatever should returns.
The method should is monkey patched into Kernel, so it can be received by any object. The Matcher returned by should holds the actual which in this case is target.
The Matcher implements the method == which does the comparison with the expected which, in this case, is the number 5. A cut down example that you can try yourself:
module Kernel
def should
Matcher.new(self)
end
end
class Matcher
def initialize(actual)
#actual = actual
end
def == expected
if #actual == expected
puts "Hurrah!"
else
puts "Booo!"
end
end
end
target = 4
target.should == 5
=> Booo!
target = 5
target.should == 5
=> Hurrah!

How can I get source and variable values in ruby tracebacks?

Here's the last few frames of a typical Ruby on Rails traceback:
And here are the last few frames of a typical Nevow traceback in Python:
It's not just the web environment either, you can make similar comparisons between ipython and irb. How can I get more of these sorts of details in Ruby?
AFAIK, once an exception has been caught it's too late to grab the context in which it was raised. If you trap the exception's new call, you could use evil.rb's Binding.of_caller to grab the calling scope, and do
eval("local_variables.collect { |l| [l, eval(l)] }", Binding.of_caller)
But that's quite a big hack. The right answer is probably to extend Ruby to allow some inspection of the call stack. I'm not sure if some of the new Ruby implementations will allow this, but I do remember a backlash against Binding.of_caller because it will make optimizations much harder.
(To be honest, I don't understand this backlash: as long as the interpreter records enough information about the optimizations performed, Binding.of_caller should be able to work, although perhaps slowly.)
Update
Ok, I figured it out. Longish code follows:
class Foo < Exception
attr_reader :call_binding
def initialize
# Find the calling location
expected_file, expected_line = caller(1).first.split(':')[0,2]
expected_line = expected_line.to_i
return_count = 5 # If we see more than 5 returns, stop tracing
# Start tracing until we see our caller.
set_trace_func(proc do |event, file, line, id, binding, kls|
if file == expected_file && line == expected_line
# Found it: Save the binding and stop tracing
#call_binding = binding
set_trace_func(nil)
end
if event == :return
# Seen too many returns, give up. :-(
set_trace_func(nil) if (return_count -= 1) <= 0
end
end)
end
end
class Hello
def a
x = 10
y = 20
raise Foo
end
end
class World
def b
Hello.new.a
end
end
begin World.new.b
rescue Foo => e
b = e.call_binding
puts eval("local_variables.collect {|l| [l, eval(l)]}", b).inspect
end

Resources