I have a simple function using gets.chomp like this:
def welcome_user
puts "Welcome! What would you like to do?"
action = gets.chomp
end
I'd like to test it using ruby's built in TestCase suite like this:
class ViewTest < Test::Unit::TestCase
def test_welcome
welcome_user
end
end
The problem is, when I run that test, the gets.chomp stops the test because it needs the user to enter in something. Is there a way I can simulate user inputs using just ruby?
You could create a pipe and assign its "read end" to $stdin. Writing to the pipe's "write end" then simulates user input.
Here's an example with a little helper method with_stdin for setting up the pipe:
require 'test/unit'
class View
def read_user_input
gets.chomp
end
end
class ViewTest < Test::Unit::TestCase
def test_read_user_input
with_stdin do |user|
user.puts "user input"
assert_equal(View.new.read_user_input, "user input")
end
end
def with_stdin
stdin = $stdin # remember $stdin
$stdin, write = IO.pipe # create pipe assigning its "read end" to $stdin
yield write # pass pipe's "write end" to block
ensure
write.close # close pipe
$stdin = stdin # restore $stdin
end
end
You first separate the 2 concerns of the method:
def get_action
gets.chomp
end
def welcome_user
puts "Welcome to Jamaica and have a nice day!"
action = get_action
return "Required action was #{action}."
end
And then you test the second one separately.
require 'minitest/spec'
require 'minitest/autorun'
describe "Welcoming users" do
before do
def get_action; "test string" end
end
it "should work" do
welcome_user.must_equal "Required action was test string."
end
end
As for the first one, you can
Test it by hand and rely that it won't break (recommended approach, TDD is not a religion).
Get the subverted version of the shell in question and make it imitate the user, and compare
whether get_action indeed gets what the user types.
While this is a practical answer to your problem, I do not know how to do 2., I only know how to imitate the user behind the browser (watir-webdriver) and not behind the shell session.
You could inject the IO dependency. gets reads from STDIN, which is class IO. If you inject another IO object into your class, you can use StringIO in your tests. Something like this:
class Whatever
attr_reader :action
def initialize(input_stream, output_stream)
#input_stream = input_stream
#output_stream = output_stream
end
def welcome_user
#output_stream.puts "Welcome! What would you like to do?"
#action = get_input
end
private
def get_input
#input_stream.gets.chomp
end
end
Tests:
require 'test/unit'
require 'stringio'
require 'whatever'
class WhateverTest < Test::Unit::TestCase
def test_welcome_user
input = StringIO.new("something\n")
output = StringIO.new
whatever = Whatever.new(input, output)
whatever.welcome_user
assert_equal "Welcome! What would you like to do?\n", output.string
assert_equal "something", whatever.action
end
end
This allows your class to interact with any IO stream (TTY, file, network, etc.).
To use it on the console in production code, pass in STDIN and STDOUT:
require 'whatever'
whatever = Whatever.new STDIN, STDOUT
whatever.welcome_user
puts "Your action was #{whatever.action}"
Related
I have a ruby file airplane.rb
with a ruby class like so -
class AirplaneSeat
attr_accessor :seat_row, :seat_column, :type, :order, :assigned_passenger
def initialize(seat_row, seat_column, type, order, assigned_passenger = 0)
#seat_row = seat_row
#seat_column = seat_column
#type = type
#order = order
#assigned_passenger = assigned_passenger
end
def get_passenger_seating
#some code
end
end # end of class
# outside the class
begin
puts "Enter the seating matrix as 2D array"
seat_matrix = JSON.parse gets.chomp
puts "Enter the number of passengers"
no_of_passengers = gets.chomp
raise "Please enter a valid passenger count" if (no_of_passengers.empty? || no_of_passengers.to_i <=0)
AirplaneSeat.get_passenger_seating(seat_matrix, no_of_passengers)
rescue Exception => e
puts "Error encountered -> #{e.message}"
end
So the ruby class has a few methods and couple of lines of code to execute outside the class, which takes input from the user and then calls the class method.
How do I go about writing test cases for this? I have the rspecs gem and spec folder setup done.
I don't really understand how to begin with the test cases.
Any pointers greatly appreciated.
Thanks!
As a simple example say we have our file for a Foo class, foo.rb:
class Foo
def call
'bar'
end
end
We can create a spec, foo_spec.rb:
require 'rspec'
require_relative 'foo'
RSpec.describe Foo do
describe '#call' do
it 'works' do
expect(described_class.new.call).to eq 'Bar'
end
end
end
And then from the command line we can run the spec:
$ rspec foo_spec.rb
I'm writing a Mastermind game in Ruby and in the constructor of the class 'Game' I want to use gets.chomp to ask the user for its name. Pretty easy, but where I run into trouble is when testing this class in RSpec, but I can't seem to properly stub out 'gets' and 'puts', because they are in the constructor not a regular method.
class Game
def initialize
puts "Please enter your name:"
#player = Player.new(gets.chomp)
end
end
describe Game do
Game.stub(:gets).and_return("Create Code AI")
Game.stub(:puts)
subject(:game) { Game.new }
describe "#new" do
its("player.name") { eql("Create Code AI") }
end
end
class Player
attr_reader :name
def initialize(name)
#name = name
end
end
I've also tried putting the stubs into 'before' and 'let' blocks amongst other things, but nothing seems to work. Any help is appreciated!
I have a method which captures stdin and stdout to help with the testing in cases like these:
require 'stringio'
module Kernel
def capture_stdout(console_input = '')
$stdin = StringIO.new(console_input)
out = StringIO.new
$stdout = out
yield
return out.string.strip
ensure
$stdout = STDOUT
$stdin = STDIN
end
end
Now, let's assume I want to test a method that interacts with stdin/stdout:
def greet
name = gets
puts "Welcome, #{name}!"
end
I would write the following test:
require 'rspec/autorun'
RSpec.describe '#say_name' do
it 'prints name correctly' do
input = 'Joe'
result = capture_stdout(input) do
greet
end
expect(result).to eql 'Welcome, Joe!'
end
end
I presented the example above to illustrate how to test both console input and output.
In your case, the test could look like this:
describe Game do
subject(:game) do
capture_stdout('Create Code AI') { return Game.new }
end
describe "#new" do
its("player.name") { eql("Create Code AI") }
end
end
Note: In order for this to work, #player should be an accessible member of Game. So, you may want to add this to your Game class:
attr_reader :player
According to my test, client_1 should be a client class which I have used to see if I can output and put it. I need to use #output variable in my client class is STILL NIL, but I assign it with the capture_output method in my client class.
#war_server.client_keys(0).puts("Hello Frodo") #works
temp_holder = client_1.capture_output #store output from server to temp holder
puts "Here is the captured input from client_1! : #{temp_holder}"#works
puts #client_1.output #DOES NOT WORK. But it should because I assin it in my class and use a reader definition
Here's the code for my classes and test. Thanks!
require 'minitest/autorun'
require 'socket'
require_relative 'WarGame_Class.rb'
require_relative 'ModifiedPlayer_Class.rb'
require_relative 'DeckClass.rb'
class WarServer
def initialize(host, port)
#socket_server = TCPServer.new(host, port)
#players = [Player.new, Player.new]
#deck = CardDeck.new
#deck.deal_cards(#players[0].cards, #players[1].cards)
game = WarGame.new
#clients = {} # keys are sockets, values are players
end
def client_keys(key)
#clients.keys[key] # this should work
end
def input #input reader function
#input
end
def close
#socket_server.close
end
def capture_input ##input client to get what they wrote
#input = #clients.keys[0].read_nonblock(1000) # arbitrary max number of bytes
end
def accept_client
#Hash here to link client to player? (or game?)
client = #socket_server.accept
#clients[client] = #players[#clients.size]
# puts "clients key 0: #{#clients.keys[0]}"
puts
# puts "clients values: #{#clients.values}"
if #clients.size == 2
start_game#####################!!!! Starts game if two clients can put client messages in start game
end
end
def start_game ##############!!!
#clients.keys[0].puts "Welcome to War. Please press enter to play your card"
#clients.keys[1].puts "Welcome to War. Please press enter to play your card"
end
end
class MockWarClient
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def output
#output
end
def input
#input
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def write_input
#input = #war_server.client_keys.write_nonblock(1000)
end
end
class WarServerTest < MiniTest::Unit::TestCase
def setup #This would be like our INITIALIZE Function
#anything is available through out all tests (i.e., instance vars)
#war_server = WarServer.new('localhost', 2012)
end
def teardown
#war_server.close
end
def test_server_capture_output_from_client
client_1 = MockWarClient.new
#war_server.accept_client
client_2 = MockWarClient.new
#war_server.accept_client
#can output #war_server.client_keys, though, if I take out the argument to pass in.
#puts "Test_Server_output #client keys #{#war_server.client_keys(player)}" #cient_1?
puts "Test_Server_output #client keys 0 #{#war_server.client_keys(0)}"
puts "Test_Server_output #client keys 1 #{#war_server.client_keys(1)}"
#war_server.client_keys(0).puts("Hello Frodo")
temp_holder = client_1.capture_output
puts "Here is the captured input from client_1! : #{temp_holder}"
#puts #war_server.input
puts #client_1.output
end
end
client_1 is a local variable, distinct from #client_1. I don't see anywhere in this code where you assign #client_1. Unless there is some code you're not sharing which does this, then #client_1 will evaluate to nil.
#client_1 is an instance variable, which is a variable on the instance of a class and exists for the duration that the object is in existence. In order to assign a value to an instance variable, you must denote it by preappending the variable name with #.
In contrast, client_1 is a local variable, which exists only within a single method or block. In your snippet, you have assigned a value to the client_1 local variable, but not to the#client_1 instance variable, which is nil until assigned.
I'm trying to run some tests on my code, but I'm running into a problem. For a very simple line of code, I get a weird error message. This test is to make sure my server can receive info from one of my clients.
The tests and file run fine without this line:
client_1.#socket.puts("This gives an error.")
Including that bit of code gives this error:
macowner:WAR3 macowner$ ruby ServerTests1.rb
ServerTests1.rb:160: syntax error, unexpected tIVAR, expecting '('
client_1.#socket.puts("Output for Server to receive.") #Error
^
Help is greatly appreciated. I get this error fairly frequently, but I have no idea what it means (even with searching for it).
enter code here
require 'minitest/autorun'
require 'socket'
require_relative 'WarGame_Class.rb'
require_relative 'ModifiedPlayer_Class.rb'
require_relative 'DeckClass.rb'
class WarServer
def initialize(host, port)
#socket_server = TCPServer.new(host, port)
#players = [Player.new, Player.new]
#deck = CardDeck.new
#deck.deal_cards(#players[0].cards, #players[1].cards)
game = WarGame.new
#clients = {} # keys are sockets, values are players
end
def client_keys(key)
#clients.keys[key] # this should work
end
def input #input reader function
#input
end
def close
#socket_server.close
end
def capture_input ##input client to get what they wrote
#input = #clients.keys[0].read_nonblock(1000) # arbitrary max number of bytes
end
def accept_client
#Hash here to link client to player? (or game?)
client = #socket_server.accept
#clients[client] = #players[#clients.size]
# puts "clients key 0: #{#clients.keys[0]}"
puts
# puts "clients values: #{#clients.values}"
if #clients.size == 2
start_game#####################!!!! Starts game if two clients can put client messages in start game
end
end
def start_game ##############!!!
#clients.keys[0].puts "Welcome to War. Please press enter to play your card"
#clients.keys[1].puts "Welcome to War. Please press enter to play your card"
end
end
class MockWarClient
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def output
#output
end
def input
#input
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def write_input
#input = #war_server.client_keys.write_nonblock(1000)
end
end
class WarServerTest < MiniTest::Unit::TestCase
def setup #This would be like our INITIALIZE Function
#anything is available through out all tests (i.e., instance vars)
#war_server = WarServer.new('localhost', 2012)
end
def teardown
#war_server.close
end
def test_server_capture_output_from_client
client_1 = MockWarClient.new
#war_server.accept_client
client_2 = MockWarClient.new
#war_server.accept_client
client_1.#socket.puts("Output for Server to receive.") #Line I need to fix
#SOCKETforSERVER
#clien_n.SOCKETforSERVER.puts''
#Replace puts with write_nonblock, perhaps
end
end
If you're trying to access the #socket instance variable then you just need another accessor method in MockWarClient:
def socket
#socket
end
and the say client_1.socket.puts(...) to use it. You could also use attr_reader to simplify things:
class MockWarClient
attr_reader :socket, :input, :output
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def write_input
#input = #war_server.client_keys.write_nonblock(1000)
end
end
That will create the three accessor methods for you.
You cannot call a method named #socket in a usual way as you are trying in client_1.#socket. That method name is very unusual because it is confusing with an instance variable, and it is doubtful that you actually have such method, but if you do, then the way to call such method is:
client_1.send(:#socket)
Okay, so I'm building a server to play the game of war as an assignment. My server needs to get input from two different sockets, and that's where I'm stuck. I've stored my sockets as keys and my players as values in a hash, but I can't get the sockets in my main program.
I can puts the hash in my main program, but I can't just get ONE socket so that I can tell my program to read from it. Here's my classes and the test I'm running:
require 'minitest/autorun'
require 'socket'
require_relative 'WarGame_Class.rb'
require_relative 'ModifiedPlayer_Class.rb'
require_relative 'DeckClass.rb'
class WarServer
def initialize(host, port)
#socket_server = TCPServer.new(host, port)
#players = [Player.new, Player.new]
#deck = CardDeck.new
#deck.deal_cards(#players[0].cards, #players[1].cards)
game = WarGame.new
#clients = {} # keys are sockets, values are players
end
def read_client_keys
#clients.key[0] #does not work
end
def close
#socket_server.close
end
def capture_input(player) ##input client to get what they wrote
#input = #clients.keys[0].read_nonblock(1000) # arbitrary max number of bytes
end
def accept_client
#Hash here to link client to player? (or game?)
client = #socket_server.accept
#clients[client] = #players[#clients.size]
# puts "clients key 0: #{#clients.keys[0]}"
puts
# puts "clients values: #{#clients.values}"
if #clients.size == 2
start_game#####################!!!! Starts game if two clients can put client messages in start game
end
end
def start_game ##############!!!
#clients.keys[0].puts "Welcome to War. Please press enter to play your card"
#clients.keys[1].puts "Welcome to War. Please press enter to play your card"
end
end
class MockWarClient
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def output
#output
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def capture_input
end
end
class WarServerTest < MiniTest::Unit::TestCase
def setup #This would be like our INITIALIZE Function
#anything is available through out all tests (i.e., instance vars)
#war_server = WarServer.new('localhost', 2012)
end
def teardown
#war_server.close
end
def test_server_capture_output_from_client
client_1 = MockWarClient.new
#war_server.accept_client
client_2 = MockWarClient.new
#war_server.accept_client
#can output #war_server.read_client_keys, though, if I take out the argument to pass in.
#puts "Test_Server_output #client keys #{#war_server.read_client_keys(player)}" #cient_1?
puts "Test_Server_output #client keys #{#war_server.read_client_keys}"
# I NEED TO JUST BE ABLE TO GET *ONE* KEY SO I CAN READ FROM THE SOCKET
#warserver.capture_input
refute(#war_server.input.empty)
end
end
You must use #keys[0] (or #keys.first) to select the first key of the Hash.
The function #key(val) does something different, returning the key corresponding to an occurrence of val in the hash.
http://www.ruby-doc.org/core-2.0.0/Hash.html#method-i-key-3F
def read_client_keys
#clients.keys[0] # this should work
end