I ran into a github spec that was failing and as I am learning how to write specs, I fixed two of them that were failing apart for the last one in with a comment #THIS ONE IS STILL FAILING. How would one make it pass?
class Team
attr_reader :players
def initialize
#players = Players.new
end
end
class Players
def initialize
#players = ["","Some Player",""]
end
def size
#players.size
end
def include? player
raise "player must be a string" unless player.is_a?(String)
#players.include? player
end
end
describe "A new team" do
before(:each) do
#team = Team.new
end
it "should have 3 players (failing example)" do
#team.should have(3).players
end
it "should include some player (failing example)" do
#team.players.should include("Some Player")
end
#THIS ONE IS STILL FAILING
it "should include 5 (failing example)" do
#team.players.should include(5)
end
it "should have no players"
end
I'll assume that the aim is to modify the spec, not to modify the code to pass the spec.
In this case, we don't actually expect #team.players to include 5; rather, we expect it to raise an exception when asked whether it includes a non-string. This can be written as follows:
it "should raise an exception when asked whether it includes an integer" do
expect {
#team.players.should include(5)
}.to raise_exception
end
For a check on the exception type and message, use raise_exception(RuntimeError, "player must be a string").
You should probably modify the descriptions of the other two specs similarly, since they no longer fail.
Related
As a beginner learning to program, it is extremely helpful to have such a supportive community out there!
I am having trouble getting this 'sample' game working. I am trying to develop a battle system where the player comes across opponents as they progress through a number of rooms. For some reason, when I run it on command prompt, it simply displays "you died" then exits. I am not sure where to go from here.
Any help would be greatly appreciated.
class Player
attr_accessor :hit_points, :attack_power
def initialize(hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= Opponent.attack_power
end
def print_status
puts "*" * 80
puts "HP: #{hit_points}/#{MAX_HIT_POINTS}"
puts "*" * 80
end
end
class Death
puts "You died"
exit(1)
end
class Opponent
def initialize (hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
puts "you come across this awful opponent"
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= player.attack_power
end
def interact(player)
while player.alive?
hurt
break if #hit_points < 1
alive?
end
if player.alive?
print "You took #{player_damage_taken} damage and dealt #{player_damage_done} damage, killing your opponent."
room
else
death
end
end
end
class Room
puts "you are now in the scary room, and you see an opponent!"
puts "You come accross a weaker opponent. It is a fish."
puts "Do you want to (F)ight or (L)eave?"
action = $stdin.gets.chomp
if action.downcase == "f"
fish = Opponent.new(2, 1)
fish.interact
else
death
end
end
Player.new(200, 1)
Room.new
class Engine
end
This is breaking because Death is a class and all the code within it is in the body of the class. That means this code will be executed when the class is defined, not at the time that death is called.
You haven't defined a method named death.
Because the Death class is tiny, and it would be awkward to name a method within it that stops the game (Death.death, Death.die, Death.run, Death.execute... Not great), and you don't need any of the advantages of a class (such as multiple instances or attributes stored in instance variables), I suggest you make the death action a part of the Player class.
class Player
# ...
def die
puts "You died"
exit(1)
end
end
Then when you've called death (the currently undefined method) Replace it with player.die.
As noted by #Kennycoc, you'll need to define a method for the death of an enemy, too.
So, it seems like you're under the impression that the code in the top level of the class is run when the class is instantiated (Class.new). This is not the case! Everything in the top level of the class is run as it is defined.
The simplest fix to get this running would be to add all the code in your top level of each class under a method named initialize this is what's run when the class is instantiated.
Also, you're using your Death class as if it were a method. You could either change it from class Death to def death or change your calls to Death.new after moving the code to the initialize method (this is not a normal pattern, but would work).
I defined this method:
# #return [Array<String>] items
def produce_items
["foo", "bar", "baz"]
end
A correct usage would be, obviously,
produce_items.each do |i|
puts i
end
But I wrote this, which silently does nothing:
produce_items do |i|
puts i
end
Is there a way to declare produce_items in such a way that my incorrect usage produces an error/exception? If MRI cannot do it, can an alternative interpreter do it? Can a static analysis tool like RuboCop or ruby-lint do it?
I suspect it may be hard because there are common idioms where methods take an optional block:
def block_optional_implicitly
if block_given?
puts "Got a block"
yield
else
puts "Did not get a block"
end
end
def block_optional_explicitly(&block)
unless block.nil?
puts "Got a block"
block.call
else
puts "Did not get a block"
end
end
(This is a reverse of the question How to require a block in Ruby?)
Yep, no way of doing this in ruby, except the block_given? checks all over the code.
Passing a block to a method that doesn't expect it does no harm. Besides something not happening (because you forgot the .each). This should be covered by unit tests. You kill two birds with one stone: make your code more regression-proof and don't get to insert fail "unexpected block" if block_given? into every method of your app.
I've been trying to test a program that simulates an elevator for two days now with little success. Here's my elevator class, the program is still a work in progress and I've also commented out some methods that might not be essential to the test I'm having trouble with. I'll gladly show more code if you think it's needed
class Elevator
attr_accessor :current_floor
GROUND = 0
TOP = 15
def initialize
#floors = [] # list of floors to travel to
#pending = [] # store floors not in direction of travel
#current_floor = GROUND
#going_up = true # cannot travel downward from ground floor
#going_down = false
end
def get_input
gets.chomp
end
def run
enter_floors
sort_floors
move_to_floor
end
def enter_floors
# prompts the user for input and calls check_floor_numbers
end
def check_floor_numbers floors
# calls validate_floors to ensure user entered '2' instead of 'two'
# if proper floor numbers have been entered this method adds the number
# to #floors array other wise it calls floor_error_message
end
def floor_error_message
puts "Please enter numbers only."
enter_floors
end
def sort_floors
# if we are on the ground floor this method sorts #floors in ascending order
# if we are on the top floor it sorts #floors in descending order
# else it calls check_direction_of_travel
end
def move_to_floor
floor = #floors[0]
if #current_floor == floor
puts "You are already on floor #{floor}"
else
print_direction
(#current_floor..floor).each { |floor| puts "...#{floor}" }
#current_floor = floor # update current_floor
#floors.delete_at(0) # remove floor from list
end
check_for_more_passengers
end
def check_for_more_passengers
puts "Are there any more passengers? (Y/N)"
answer = (get_input).upcase
answer == 'Y' ? run : check_next_move
end
def check_next_move
if #floors.empty? && #pending.empty?
end_ride
else
move_to_floor
end
end
def check_direction_of_travel
# not implemented - add floor to appropriate array depending on dir
# of travel
end
def end_ride
puts "\n\nEND."
end
def print_direction
msg = " "
#going_up ? msg = "Going Up!" : msg = "Going Down!"
puts msg
end
end
I'm trying to test that the elevator can move to a specific floor. At first I was having trouble testing input from the console without running the program itself. I asked a question about this and was referred to this answer in a different question. The answer in question extract gets.chomp to a separate method then overrides the method in the tests. I ended up with something like this:
describe "it can move to a floor" do
before do
##moves = ["2", "N"]
def get_input; ##moves.next end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
Problem: get_input was not properly overidden and running the test suit prompted the user for input so it was suggested that I open the Elevator class in the test itself to ensure that the method was properly overridden. Attempting to do so eventually led to a test like this:
describe "it can move to a floor" do
before do
class Elevator
attr_accessor :current_floor
##moves = ["2", "N"]
def get_input; ##moves.next end
def run; end
end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
I had to override run and add an attr_accessor for current_floor because I was getting method missing errors.
Problem: This test gives the following error:
1) Failure: it can move to a floor#test_0001_should move to floor 2
[elevator_test.rb:24]: Expected: nil Actual: 2
I've tried to tidy up the Elevator class as much as possible and keep the methods as simple as I could given the parameters of the program.
Can anyone point me in the right direction towards getting this solved, with maybe pseudocode examples (if possible) to demonstrate how I should approach this problem if the answer is to refactor.
Please bear in mind that I'd also like to implement other tests like checking that the elevator class can maintain a list of floors, or that it can change direction, in the future when you answer.
Your test class ElevatorTest is redefining the Elevator to override method get_input, but it is not opening the class Elevator defined in elevator.rb, but instead it is sort of creating a new class Elevator which happens to be defined inside the class ElevatorTest. Remember every class is also a module, so now you have a new class ElevatorTest::Elevator.
To fix this issue, I have made some changes to elevator_test.rb which is shown below.
gem 'minitest', '>= 5.0.0'
require 'minitest/spec'
require 'minitest/autorun'
require_relative 'elevator'
class Elevator
##moves = ["2", "N"].each
def get_input; ##moves.next end
end
class ElevatorTest < MiniTest::Test
def test_working
assert_equal(1, 1)
end
describe "it can move to a floor" do
before do
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
end
Also, please remember to use .each while defining ##moves - it returns an enumerator. We can call .next only on an enumerator
Heys guys i am beginner in rspec Can anybody help me with this problem.
and my test returns the number of sides that the die possesses fail,what i am doing wrong??
require 'rspec'
require_relative 'die'
describe Die do
describe '#initialize' do
it 'expects a single argument' do
expect(Die.instance_method(:initialize).arity).to eq 1
end
it 'raises ArgumentError if sides are < 1' do
expect {
Die.new(-1)
}.to raise_error(ArgumentError)
expect {
Die.new(0)
}.to raise_error(ArgumentError)
end
end
describe 'Instance method' do
before(:each) do
#sides = rand(50)
#die = Die.new(#sides)
end
context '#num_of_sides' do
it 'expects method to have no arguments' do
expect(Die.instance_method(:num_of_sides).arity).to eq 0
end
it 'returns the number of sides that the die possesses' do
expect(#die.num_of_sides).to eq #sides
end
end
context "#roll" do
it 'expects roll method to have no arguments' do
expect(Die.instance_method(:roll).arity).to eq 0
end
it "returns a random number between 1 and number_of_sides" do
rolls = Array.new(10000) {#die.roll}.uniq.sort
possible_values = (1..#sides).to_a
expect(rolls).to eq possible_values
end
end
end
end
Ruby Die Class:
class Die
def initialize(sides)
raise ArgumentError if sides < 1
end
def num_of_sides(sides=nil)
if sides!=nil
Random.rand(50)
else
0
end
end
def roll()
end
end
Output: 6 examples, 2 failures, 4 passed
You have not yet implemented the Die class so that it remembers the number of sides it was instantiated with.
To do this, you would normally use an instance variable e.g. #num_of_sides, and set it equal to the sides param in the constructor, after you have checked for a valid input.
To read the number of sides you can either just return the value of #num_of_sides (alter your existing method to be much simpler, it doesn't need to take any parameters, and only needs to return the value you have stored, no tests/logic or randomness required), or you can use the short-cut attr_reader :num_of_sides which creates that method for you.
It would be worth reviewing any notes you have on Ruby instance variables, as this seems to be something you still need to get to grips with. Or you could take a look at this Ruby Monk Primer on creating classes.
I'm writing a very simple dungeon adventure game in Ruby (practice for a newb). I want to address the player by name throughout, so naturally I want to be sure that if a player passes an empty name to my Player initialize method if tells them they can't do that and then prompts them to retry.
class Player
attr_accessor :name, :location
def initialize(name)
if name.empty? == false
#name = name
else
puts "You did not enter your name! Try again, please"
load 'game.rb'
end
end
end
The file name is 'game.rb' so I'm basically reloading the entire file here each time the player decides not to enter their name. Which is stupid...
It works, but in the worst way...I had to be 'clever' about where I exited the program so that the player would be insulated from the fact that I'm basically going all Inception on everyone and launching a game within a game every time the player starts a new game and neglects to enter their name. For instance, if they don't notice 3 times that they need to input their name because they're not paying attention, I effectively have 4 games running (the original, and the 3 the prompted by not entering their name) and they'd either need to end each of those games when they're tired of playing, or else I had to basically exit the whole thing hard at one keyword.
My question is this: is there a way to write my error "You did not enter..." exit the current game session, and relaunch the game? All I really want to do is ensure an empty string doesn't get passed to my initialize method, maybe by raising an exception, and then start over at the beginning of the script without having a game-within-a-game.
Here is a link to the full code for more insight: http://repl.it/8QY
It's hard to know without seeing the whole game structure, but your main file could look like
game_initialized = false
while ! game_initialized
begin
# here, initialize the game, including initialization
game_initialized = true
rescue NoNameError
# do nothing, but it will restart the game
end
end
# now play the game
And in your constructor, you add
class NoNameError < Exception
end
class Player
attr_accessor :name, :location
def initialize(name)
if name.empty? == false
#name = name
else
puts "You did not enter your name! Try again, please"
raise NoNameError
end
end
end
That should get you going.
I think Vincent's answer is good, it certainly is more OO than mine but a simple approach would be to do something like this at game start:
print "Welcome! "
in_name = ""
while true
puts "What is your name?"
in_name = gets.chomp
in_name.empty? ? (puts "You must enter a name before continuing") : break
end
Example:
Welcome! What is your name?
You must enter a name before continuing
What is your name?
Anthony
If you want your logic to be in Player, you can try something like this:
class Player
attr_accessor :name, :location
def initialize(name)
#name = keep_asking_for_name_if_not_already_entered(name)
end
def keep_asking_for_name_if_not_already_entered(name)
return name unless name.empty?
loop do
puts 'You did not enter your name! Enter a name, please:'
name = gets.chomp
break(name) unless name.empty?
end
end
end