I'm new to both Ruby and RSpec, and have already spent hours trying to write the first steps for testing an interactive tic tac toe program, but haven't located any useful answers to the error I'm getting:
> 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
When you start the game, it prompts whether you want to play (y/n). I've already attempted different variations of using mocking and stubs but always get this same error. I imagine I'm missing something very basic but cannot see it.
Question: how would one adequately address (using RSpec) this first prompt of the game?
The game uses an independent method (start_game) but not a class to start. I'm looking for some way mock or stub this with some default input (like y or n) as I want the tests to be automated. All the examples I see use a class to start a program, however (rather than a stand-alone method like here).
Currently in spec/tic_tac_toe_spec.rb there are some let keywords for :output and :game which I found from an example somewhere but obviously this doesn't work.
Edit I want to test the following method. The RSpec code keeps choking on the choice = gets.chomp.downcase line.
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
You are receiving the error because you are passing the --format documentation as a parameter to RSpec.
gets reads the ARGV that have been passed. This also includes the RSpec parameters. You should use STDIN.gets so that you only read standard input and not the parameters.
You can read more about in this question:
https://stackoverflow.com/a/19799223/6156030
A simple approach could be:
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
You can also specify multiple return values for gets, which will be used sequentially as the test runs.
The alternative approach you seem to have started (but not fully implemented) is to inject an explicit output (and, presumably, one day an input) object into the game.
This would indeed be a cleaner approach from the purist's point of view - i.e. not stubbing the global objects like $stdin - but is probably overkill for your initial version of the code. Unless you plan doing something fancy like running parallel specs, I wouldn't worry about that.
Edit: After looking at your actual code in more detail, I see the problem. You're defining global methods which are doing multiple things and are tightly coupled. This makes writing tests much harder!
Here I have added a working test example:
https://github.com/tom-lord/tic_tac_toe_rspec/commit/840df0b7f1380296db97feff0cd3ca995c5c6ee3
However, going forward in order to simplify this my advice would be to define all each method within an appropriate class, and make the code less procedural. (i.e. Don't just make the end of one method call the next method, in a long sequence!) This refactoring is perhaps beyond the scope of a StackOverflow answer though.
You need to stub and mock gets method in your specs:
yes_gets = double("yes_gets")
allow($stdin).to receive(:gets).and_return(yes_gets)
Which then you can make it respond to #chomp:
expect(yes_gets).to receive(:chomp).and_return('Y')
You can cover the similar method call for downcase by returning this double object itself.
You can also do the similar work for mock object for your 'N' case where you'd expect game to exit when player inputs an N(No):
no_gets = double("no_gets")
allow($stdin).to receive(:gets).and_return(no_gets)
expect(no_gets).to receive(:chomp).and_return('N')
Related
I want to monkey patch a String#select method, and want to create a test suite that checks that it doesn't use Array#select.
I tried creating a bunch of tests using to_not receive, using both to_not receive(Array:select) and just to_not receive(:select). I also tried using an array (string.chars) instead of the string . Google and stack overflow did not bring an answer.
describe "String#select" do
it "should not use built-in Array#select" do
string = "HELLOworld".chars
expect(string).to_not receive(Array:select)
end
end
Expected: a working test suite that checks that Array#method has not been used in the whole method.
Actual output: I'm getting an error that not enough arguments have been used. Output log below:
1) RECAP EXERCISE 3 Proc Problems: String#select should not use built-in Array#select
Failure/Error: expect(string).to_not receive(Array:select)
ArgumentError:
wrong number of arguments (given 0, expected 1..4)
# ./spec/problems_spec.rb:166:in `select'
# ./spec/problems_spec.rb:166:in `block (4 levels) in <top (required)>'
First of all: tests are supposed to check the results of methods called, not the way they are implemented. Relying to much on this would get you in trouble.
But there might be a legit reasons to do it, but think hard of you can test it other way:
Let's say that String#select uses Array#select internally, and the latter is buggy under some circumstances. It's better to make a test, setting up the universe in a way that would trigger the bug and check that the buggy behavior is not present . Then patch the String#select and have the test green. It's much better approach, because the test now tells everyone why you're not supposed to use Array#select internally. And if the bug is removed, it's the easiest thing under the sun to remove patch and check if the spec is still green.
That being said, if you still need that you can use expect_any_instance_of to acomplish that, for example this spec would fail:
class String
def select(&block)
split.select(&block) # remove this to make the spec pass
end
end
specify do
expect_any_instance_of(Array).not_to receive(:select)
'foo'.select
end
If you don't want to use expect_any_instance_of (because reasons), you can temporarily overwrite a method in a class to fail:
class String
def select(&block)
#split.select(&block)
end
end
before do
class Array
alias :backup_select :select
def select(*)
raise 'No'
end
end
end
after do
class Array
alias :select :backup_select # bring the original implementation back
end
end
specify do
expect { 'foo'.select }.not_to raise_error
end
Aliasing is needed to bring back the original implementation, so you don't mess up the specs that are run after this one.
But you can see how involved and messy this approach is.
Anyway - what you're trying to achieve is most probably a design issue, but it's hard to tell without more details.
I am using minitest for the first time and I am having trouble understanding how to write my first test method. Can anyone assist in helping me understand what I should be testing in the below Player method get_name?
class Player
def get_name(player)
puts `clear`
center("#{player}, whats your name bro/ladybro?")
#name = gets.chomp
until #name =~ /\A[[:alnum:]]+\z/
center("you can do a combination of alphanumeric characters")
#name = gets.chomp
end
end
end
This is what I have in my test file, I was thinking I am just suppose to test the regex to make sure it takes alpha and numeric characters.
class TestPlayer < Minitest::Test
def test_get_name
describe "get_name" do
it "should allow an input of alphanumeric characters" do
assert_match(/\A[[:alnum:]]+\z/, "test_string123")
end
end
end
end
but when I run the tests, nothing seems to happen, I would imagine I am suppose to have 1 assertion.
Run options: --seed 10135
# Running:
.
Finished in 0.001565s, 638.9776 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 0 errors, 0 skips
can anyone assist in demonstrating how I should write a test for this scenario? thanks.
Minitest test may be described as follows (Assertion syntax):
Its just a simple Ruby file which has a class thats typically a subclass of Minitest::Test.
The method setup will be called at the first; you can define objects that you may require in every test. Eg: Consider assigning an instance of the Player object here in an instance variable in setup method so that you can use it elsewhere in the test class.
A test is defined inside a method that starts with the string: test_; any other method can be used to reduce duplication of code but it won't be considered part of tests.
Typically you should think about testing the return value of a method you want to test.
Testing a method with external input is more convoluted, I would suggest to start with testing methods that have testable output.
I am working with a custom testing framework and we are trying to expand some of the assert functionality to include a custom error message if the assert fails. The current assert is called like this:
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end
and we want something with the functionality of:
assert_compare(first_term, :neq, second_term, error_message) do
puts 'foobar'
end
so that if the block fails the error message will describe the failure. I think this is ugly, however, as the framework we are moving away from did this and i have to go through a lot of statements that look like:
assert.compare(variable_foo['ARRAY1'][2], variable_bar['ARRAY2'][2], 'This assert failed because someone did something unintelligent when writing the test. Probably me, since in am the one writing this really really long error statement on the same line so that you have to spend a quarter of your day scrolling to the side just to read it')
This type of method call makes it difficult to read, even when using a variable for the error message. I feel like a better way should be possible.
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end on_fail: 'This is a nice error message'
This, to me, is the best way to do it but i don't know how or if it is even possible to accomplish this in ruby.
The goal here is to make it as aesthetic as possible. Any suggestions?
You could make on_fail a method of whatever assert_compare returns and write
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end.on_fail: 'This is a nice error message'
In short, no. Methods in ruby take a block as the final parameter only. As Chuck mentioned you could attempt to make the on_fail method a method of whatever assert_compare returns and that is a good solution. The solution I've come up with is not what you are looking for, but it works:
def test block, param
block.call
puts param
end
test proc { puts "hello"}, "hi"
will result in
"hello"
"hi"
What I've done here is create a Proc (which is essentially a block) and then passed it as a regular parameter.
The following is a section of exercise #5 (Silly Blocks) from Test-First.org which I'm trying to crack as I learn on my own, in preparation for a Ruby class.
Each exercise comes with an RSpec '_spec.rb' file and the user is expected to write a corresponding Ruby code '.rb' file, and continue to "rake it" until all the RSpec tests (examples) within are satisfied. At least that is my interpretation and I've been able to get through the first four exercises, however, the RSpec syntax in this exercise has me stumped. (Unfortunately, I'm not only fairly new to coding, I'm definitely very new to RSpes and I haven't been able to find a good newbie-grade intro to RSpec/TDD online).
Thus, I'm hoping a resident RSpec expert might help. Basically, I'd like to know what exactly is the following RSpec syntax telling me to write for code?
require "silly_blocks"
describe "some silly block functions" do
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
result.should == "olleh"
end
...
I assumed that I'm to write a method called 'reverser' which accepts a string argument, and returns the sting reversed, such as:
def reverser(string)
return string.reverse
end
Alas, that is clearly not correct - the rake fails miserably:
some silly block functions
reverser
reverses the string returned by the default block (FAILED - 1)
Failures:
1) some silly block functions reverser reverses the string returned by the def
ault block
Failure/Error: result = reverser do
ArgumentError:
wrong number of arguments (0 for 1)
# ./05_silly_blocks/silly_blocks.rb:3:in `reverser'
# ./05_silly_blocks/silly_blocks_spec.rb:15:in `block (3 levels) in <top (r
equired)>'
I suspect it has something to do with passing "default code blocks" but I'm not sure how to structure that. There are many more methods to write in this exercise, however, if I can get some clarity on the initial one, I think I can work out the rest!
Major thanks, Danke sehr, Muchas gracias!! :)
As far as I know, since this method takes a block and does something with it, you need to define the method to take a block, rather than an argument. So to enable the method to do this:
reverser do
"hello"
end
You would write it something like:
def reverser
yield.reverse
end
or:
def reverser(&block)
block.call.reverse
end
Now the above methods will work when a block is passed to it: reverser { "hello" }, but not when an argument is used: reverser("hello").
I'm trying to run a scenario several (30) times in order to get a nice statistical sample. However the block is only executing once; each subsequent time results in the scenario being called and not executing (although it says that the scenario did successfully complete with a time of around 5 ms).
Around('#mass_benchmark') do |scenario, block|
$seconds_taken = "SECONDS TAKEN NOT SET"
#time_array = []
30.times do
before_hook(scenario)
block.call
after_hook(scenario)
#time_array << $seconds_taken
end
write_time_array_to_file(#time_array, scenario_name)
end
The tag #mass_benchmark executes this block, as opposed to ~#mass_benchmark, which just executes the scenario normally. The methods before_hook and after_hook replicate the Before ('~#mass_benchmark') and After ('~#mass_benchmark') hooks (which actually just call the same method).
The variable $seconds_taken is set around the specific area for which I am timing. I am not timing the whole test there, just a critical portion of it; the remainder of the test is getting to that point, etc, which is not to be part of the timed portion, so I cannot just move the timing portion outside of this.
The issue may be with something I'm doing in those methods, but as far as I can tell, everything works normally (as indicated by well-placed puts statements). Any ideas are appreciated!
Currently Cucumber does not seem to support calling the block twice in an around hook. This can be demonstrated by the following feature file:
Feature: This scenario will print a line
Scenario: Print a line
When I print a line
And step definitions:
Around do |scenario, block|
Kernel.puts "START AROUND, status=#{scenario.status}"
block.call
Kernel.puts "BETWEEN CALLS, status=#{scenario.status}"
block.call
Kernel.puts "END AROUND, status=#{scenario.status}"
end
When /^I print a line$/ do
Kernel.puts "IN THE STEP DEFINITION"
end
When this is executed, Cucumber will print:
Scenario: Print line # features/test1.feature:3
START AROUND, status=skipped
IN THE STEP DEFINITION
When I print a line # features/test.rb:9
BETWEEN CALLS, status=passed
When I print a line # features/test.rb:9
END AROUND, status=passed
Evidently since the status of the scenario is already "passed", Cucumber does not re-execute it, though the output formatter receives the steps. I have not found any way to "reset" the status in the scenario API to get them to be re-run.
There are other problems with around hooks as well, for example you cannot set variables to the World in around hooks (like you can in before hooks). See also Cucumber issues 52 and 116 for more gory details.
One possibility might be to keep the passed-in block as it is, and call the ".call" method on a duplicate?
Something like (untested):
Around do |scenario, block|
30.times do
duplicate = block.dup
before_hook(scenario)
duplicate.call
after_hook(scenario)
end
end
Just make sure not to use ".clone" on the block, since clone will create an object with the same Id, resulting in every change made to the duplicate also affecting the original.