Testing with RSpec - Output activity messages to STDOUT in Ruby - ruby

I'm looking for some help outputting the activity messages into the command line window. I know this may seem backwards but this is the task I've been given. I've already written tests so that they all pass but I need to convert the below activity into the command line window. It's just a game that resembles the Impossible Machine game.
Firstly I need to create a process which starts the Impossible Machine, then simulate each of the activities being initiated in succession before finishing.
Of what I understand, all the messages displayed should be sent to the STDOUT channel. These are some of the tests that have been written:
module ImpossibleMachine
# Input and output constants processed by subprocesses
DOWN_ARROW = 1
UP_ARROW = 2
RIGHT_ARROW = 3
REPEAT_ARROW = 4
END_PROCESS = 5
START_CURRENT = 6
# RSpec Tests
describe Game do
describe "#start The impossible machine game" do
before(:each) do
#process = []
#output = double('output').as_null_object
#game = Game.new(#output)
end
it "sends a welcome message" do
#output.should_receive(:puts).with('Welcome to the Impossible Machine!')
#game.start
end
it "should contain a method created_by which returns the students name" do
myname = #game.created_by
myname.should == "My Name"
end
it "should perform lifts_lever_turns_wheel activity which returns REPEAT_ARROW" do
#output.should_receive(:puts).with("Input: #{UP_ARROW}, Activity: Heave_ho_squeek_squeek")
#process[1] = #game.lifts_lever_turns_wheel(UP_ARROW)
#process[1].should == REPEAT_ARROW
end
it "sends a finishing message" do
#output.should_receive(:puts).with('...Game finished.')
#game.finish
end
end
end
My only knowledge is that I need to start the module like this and then proceed to add code below it so that it outputs the activity messages to the command line:
module ImpossibleMachine
#process = []
g = Game.new(STDOUT)
Hope that makes sense.

It is not very clear from your question - you want the game to show its output to STDOUT when you run the rspec?
If this is the case, I'll explain why in your code as you post it, it does not happen:
When you create the new game #game you create it with Game.new(#output). The #output is a double, which means that it is not really an output object at all, but it is a mock object instead.
This is totally fine, by the way. The only problem with it is that it doesn't actually print anything to the console.
If you want to make the tests, while actually printing to the console, you should pass the actual STDOUT object:
before(:each) do
#process = []
#output = STDOUT
#game = Game.new(#output)
end
This will almost work, as it will print all messages except the ones you stub in your tests #output.should_receive(...). To make those work, you should add and_call_original to each expectation:
#output.should_receive(:puts).with('Welcome to the Impossible Machine!').and_call_original

You can do this without doubles:
it "should perform lifts_lever_turns_wheel activity which returns REPEAT_ARROW" do
expect(STDOUT).to receive(:puts).with("Input: #{UP_ARROW}, Activity: Heave_ho_squeek_squeek")
#process[1] = #game.lifts_lever_turns_wheel(UP_ARROW)
#process[1].should == REPEAT_ARROW
end

Related

Handling failures in data driven testing using rspec

I am using rspec to do some data driven testing. My test reads from a csv file, grabs an entry which is inserted into the text box on the page and is then compared to expected text which is also read from the csv file. All this is working as expected, I am able to read and compare without any issues.
Below is my code:
Method for reading csv file:
def user_data
user_data = CSV.read Dir.pwd + '/user_data.csv'
descriptor = user_data.shift
descriptor = descriptor.map { |key| key.to_sym }
user_data.map { |user| Hash[ descriptor.zip(user) ] }
end
Test:
describe "Text box tests" do
before :all do
#homepage = Homepage.new
end
it "should display the correct name" do
visit('http://my test url')
sleep 2
user_data.each do |entry|
#homepage.enter_name(entry[:name])
#homepage.click_go()
sleep 2
begin
expect(page).to have_css("#firstname", text: entry[:expected_name])
end
end
end
end
The problem is with failures. If I have a failure with one of the tests (i.e the expected text is not displayed on the page) then the test stops and all subsequent entries in the csv are not tested. If I put in a rescue after the expect statement like this:
rescue Exception => error
puts error.message
Then the error is logged to the console, however at the end of my test run it says no failures.
So basically I am looking for is, in the event of a failure for my test to keep running(until all entries in the csv have been covered), but for the test run to be marked as failed. Does anyone know how I can achieve this?
Try something like this:
context "when the user is on some page" do
before(:context) { visit('http://example.org/') }
user_data.each do |entry|
it "should display the correct name: #{entry[:name]}" do
#homepage.enter_name(entry[:name])
#homepage.click_go
expect(page).to have_css("#firstname", text: entry[:expected_name])
end
end
end
You will also need to change def user_data to def self.user_data
I would advise mapping over the entries and calling the regular capybara method has_css? instead of the rspec helper method. It would look like this:
results = user_data.map do |entry|
#homepage.enter_name(entry[:name])
#homepage.click_go()
sleep 2
page.has_css?("#firstname", text: entry[:expected_name])
end
expect(results.all?) to be_truthy
if you want to keep track of which ones failed, you cann modify it a bit:
missing_entries = []
user_data.each do |entry|
#homepage.enter_name(entry[:name])
#homepage.click_go()
sleep 2
has_entry = page.has_css?("#firstname", text: entry[:expected_name])
unless has_entry
missing_entries.push entry[:expected_name]
end
end
expect(missing_entries).to be_empty

end to end test of a ruby console app

I have a ruby console app that you run with an argument, then once running outputs some text to the screen, asks for some more user input and then outputs some more text to the screen. I want to do an end to end test on this app and I don't know how. If I were writing an end to end test for an REST API, I would just hit the public endpoint, follow the links and then have an expect statement on the output. Easy. But on a console app I have no idea how to do the same thing. Are there any gems for stepping through a console app in the context of a test? I've been looking all day but can't find anything.
ANY help appreciated.
Inspired by this gem which has a fairly simple implementation, I wrote a method which captures console input & output and can, therefore, be used in tests:
require 'stringio'
module Kernel
def emulate_console(console_input)
$stdin = StringIO.new(console_input)
out = StringIO.new
$stdout = out
yield
return out
ensure
$stdout = STDOUT
$stdin = STDIN
end
end
This method captures console output, and also provides as input the string value which you specify in the console_input parameter.
Basic usage
Here's a simple usage of the emulate_console method:
out = emulate_console("abc\n") do
input = gets.chomp
puts "You entered: #{input}!"
end
The return value out is a StringIO object. To access its value, use the #string method:
out.string
=> "You entered: abc!\n"
Note that the input contains a newline character (\n) to simulate pressing the ENTER key.
Testing
Now, let's assume that you want to test this method, that uses both stdin and stdout:
def console_add_numbers
x = Integer(gets)
y = Integer(gets)
puts x + y
end
The following RSpec test tests the happy path of this code:
require 'rspec/autorun'
RSpec.describe '#console_add_numbers' do
it 'computes correct result' do
input = <<-EOS
2
3
EOS
output = emulate_console(input) { console_add_numbers }
expect(output.string.chomp).to eql '5'
end
end

how do you mock dependent methods using rspec

I'm trying to write a custom parser for my cucumber results. In doing so, I want to write rspec tests around it. What I currently have is as follows:
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
The problem is, determine_test_results has a sub method called determine_step_results, which means this is really an integration test between the 2 methods and not a unit test for determine_test_results.
How would I mock out the "response" from determine_step_results?
Assume determine_step_results returns {"status"=>"passed", "scenario_line"=>4}
what I have tried:
pcr.stub(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
and
allow(pcr).to receive(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
You could utilize stubs for what you're trying to accomplish. Project: RSpec Mocks 2.3 would be good reading regarding this particular case. I have added some code below as a suggestion.
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
# calls on pcr will return expected results every time determine_step_results is called in any method on your pcr object.
pcr.stub!(:determine_step_results).and_return(expected_results)
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
If all what determine_test_results does is call determine_step_results, you should not really test it, since it is trivial...
If you do decide to test it, all you need to test is that it calls the delegate function, and returns whatever is passed to it:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
subject.determine_test_results(input).should == result
end
end
end
If it is doing anything more (like adding the result to a larger hash) you can describe it too:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
expect(subject.larger_hash).to receive(:merge).with(result)
subject.determine_test_results(input).should == result
end
end
end

Capybara redirect stderr

While running automated test scripts I will oftentimes get the following warning messages and others:
QFont::setPixelSize: Pixel size <= 0 (0)
QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.
I've searched around and these outputs have nothing to do with my test scripts. They don't impact the results in any way. Thus, I don't wish to see them.
While looking for a way to solve this, I found code which is supposed to redirect stderr to a class which will filter out specific messages. However, when I try to use this code, none of my scripts work.
The class that suppresses the warnings:
class WarningSuppressor
# array to hold warnings to suppress
SUPPRESS_THESE_WARNINGS = [
'QFont::setPixelSize: Pixel size <= 0 (0)',
'QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.'
]
class << self
def write(message)
if suppress_warning? message
0
end
end
def suppress_warning? message
SUPPRESS_THESE_WARNINGS.any? { |suppressable_warning| message.chomp.include? suppressable_warning }
end
end
end
The configuration code that is supposed to redirect stderr:
Capybara.configure do |config|
config.default_driver = :webkit
config.javascript_driver = :webkit
config.run_server = false # prevents Capybara from booting up a rack application automatically
config.app_host = 'http://local.xxxxxxxx.com'
# Sends output to a custom warning supressor
config.register_driver :webkit do |app|
Capybara::Driver::Webkit.new(app, stderr: WarningSuppressor)
end
# 10 second wait for ajax to finish
config.default_wait_time = 10
end
If I insert a binding.pry in the block statement, app is nil but Capybara::Driver::Webkit exists.
Does anyone have a better way/way to fix this; a method of hiding certain warnings from being displayed while running my automated scripts?

Discussion with a sub-process, using Ruby with IO and threading

I am trying to use IO.popen in order to put (with .puts method) and to get (with .gets method) messages from a process to its sub-process.
I am not very experimented and I have a question about. Having the following code, I have an error because it is not possible to write in a closed stream.
class Interface
def initialize(path)
#sub_process = IO.popen(path, 'w+')
end
def start!
if ok?
#sub_process.puts 'Hello', 'my name is ...'
# and more...
end
end
protected
def ok?
is_ready?(#sub_process) && is_cool?(#sub_process)
end
def is_ready?(sub_process)
reply = process_command(sub_process, 'are u ready?')
reply.chomp.match(/yes_i_am_ready$/)
end
def is_cool?(sub_process)
reply = process_command(sub_process, 'are u cool?')
reply.chomp.match(/yes_i_am_cool$/)
end
def process_command(sub_process, command)
rdr = Thread.new { sub_process.read } # alternative: io.readlines
sub_process.puts "#{command}"
sub_process.close_write
rdr.value # joins and fetches the result
end
end
a = Interface.new("./program")
a.start!
(...) in `write': not opened for writing (IOError)
As we can see, this error occur during is_cool? test (as explained at: http://ruby-doc.org/core/classes/IO.html#M002289).
But if I try to comment in process_command method the line:
# sub_process.close_write
the script seems to sleep... infinitely :s
I believe that it is not possible to open again a closed stream. And I can't create an other IO.popen instance of my program "./program" because it needs to be initialized with some command (like 'are u ready?' and 'are u cool?') at the beginning, before I use it (by sending and receiving messages like a simple discussion).
How changes can I do over the current code in order to solve this problem?
Edit: in other words, I would like to establish a such communication (according to a given protocol):
Parent message: Child answer:
-------------- ------------
'are u ready?' 'yes_i_am_ready'
'are u cool?' 'yes_i_am_cool'
'Hello' 'foo'
'my name is ...' 'bar'
Many thanks for any help.
Perhaps it will help to have a working example. Here's one, tested and known to work in MRI 1.8.7 on Linux.
bar.rb
#!/usr/bin/ruby1.8
begin
loop do
puts "You said: #{gets}"
$stdout.flush
end
rescue Errno::EPIPE
end
foo.rb
#!/usr/bin/ruby1.8
class Parent
def initialize
#pipe = IO.popen(CHILD_COMMAND, 'w+')
end
def talk(message)
#pipe.puts(message)
response = #pipe.gets
if response.nil?
$stderr.puts "Failed: #{CHILD_COMMAND}"
exit(1)
end
response.chomp
end
private
CHILD_COMMAND = './bar.rb'
end
parent = Parent.new
puts parent.talk('blah blah blah')
puts parent.talk('foo bar baz')
foo.rb output
You said: blah blah blah
You said: foo bar baz
A closed IO can not be used anymore. You should not close an IO if you intend on still using it.
If you remove the IO#close_write there still remains the problem with your code in the following line.
rdr = Thread.new { sub_process.read }
IO#read read's until EOF. So until the stream get's closed it never terminates. You mentioned IO#readline in your code, this would be the better alternative. Using IO#readline your program would only hang if the popend process never send's a newline.
Another problem with popen is the following. IO#popen create's a new process. Process's may be killed by you, other users, memory shortages, …. Don't expect your process to always run all the time. If the process is killed IO#readline will throw an EOFError, IO#read will return imidiatley. You can determine the termination reason with the following code.
Process::wait(io.pid)
status= $?
status.class # => Process::Status
status.signaled? # killed by signal?
status.stopsig # the signal which killed it
status.exited # terminated normal
status.exitstatus # the return value
status.ki
Does it help to use this form of Thread.new?
rdr = Thread.new(sub_process) {|x| x.readlines }

Resources