Gem not outputting the expected text - ruby

I've completed the First Gem section of this tutorial. However, in the Requiring More Gems section when I tried modifying my hola.rb as such:
class Hola
def self.hi(language = :english)
translator = Translator.new(language)
translator.hi
end
end
require 'hola/translator'
and added this translator.rb file:
class Hola::Translator
def initialize(language)
#language = language
end
def hi
case #language
when :spanish
"hola mundo"
else
"hello world"
end
end
end
If I typed in these commands in IRB, I should get identical outputs:
% irb -Ilib -rhola
irb(main):001:0> Hola.hi(:english)
=> "hello world"
irb(main):002:0> Hola.hi(:spanish)
=> "hola mundo"
However, I'm not getting proper results. I tried modifying my gemspec and reinstalling the gem, but no success. What do you think I did wrong in this case?
stanley#ubuntu:~/Github/webdev_class/ruby/hola_stanley/lib$ irb -Ilib -rhola
irb(main):001:0> Hola.hi(:english)
=> "hello world"
irb(main):002:0> Hola.hi(:spanish)
=> "hello world"
irb(main):003:0> quit

I don't see, what's wrong, but are you sure there was no type and you requested the file you expected?
I would modify the code a bit:
class Hola::Translator
def initialize(language, exceptions = false)
#language = language
#exceptions = exceptions
end
def hi
case #language
when :spanish
"hola mundo"
when :english
"hello world"
else
raise ArgumentError, "Unsupported language #{#language.inspect}" if #exceptions
"hello world"
end
end
end
You support two languages: Spanish and English. Other languages are detected and you can select, if you get an error or an alternative (english) text.
You can start it with:
class Hola
def self.hi(language = :english)
translator = Translator.new(language, true) ## <-- Modified
translator.hi
end
end
require 'hola/translator'
One advantage of the expection: You can see, which file really calls the exception.
Some other remarks:
I would recommend to define Hola as a module, not a class.
Instead the raise you could define a logger and report an error.
Example:
require 'log4r'
module Hola
LOG = Log4r::Logger.new('Hola')
class Translator
def initialize(language)
#language = language
end
def hi
case #language
when :spanish
"hola mundo"
when :english
"hello world"
else
LOG.error("Unsupported language #{#language.inspect}")
"hello world"
end
end
end
def self.hi(language = :english)
translator = Translator.new(language)
translator.hi
end
end #module Hola
p Hola.hi
p Hola.hi(:english)
p Hola.hi(:spanish)
p Hola.hi(:german)
puts "Activate Hola-warnings"
Hola::LOG.outputters << Log4r::StdoutOutputter.new('stdout')
p Hola.hi
p Hola.hi(:english)
p Hola.hi(:spanish)
p Hola.hi(:german)
After activating the logger, you are informed, if you have a type in :spanish (that's the error I would expect).

Related

Writing a test for a case statement in Ruby

I'm trying to write a test for a case statement using minitest. Would I need to write separate tests for each "when"? I included my code below. Right now it just puts statements, but eventually it's going to redirect users to different methods. Thanks!
require 'pry'
require_relative 'messages'
class Game
attr_reader :user_answer
def initialize(user_answer = gets.chomp.downcase)
#user_answer = user_answer
end
def input
case user_answer
when "i"
puts "information"
when "q"
puts "quitter"
when "p"
puts "player play"
end
end
end
This answer will help you. Nonetheless I'll post one way of applying it to your situation. As suggested by #phortx when initializing a game, override the default user-input with the relevant string. Then by using assert_output we can do something like:
#test_game.rb
require './game.rb' #name and path of your game script
require 'minitest/autorun' #needed to run tests
class GameTest < MiniTest::Test
def setup
#game_i = Game.new("i") #overrides default user-input
#game_q = Game.new("q")
#game_p = Game.new("p")
end
def test_case_i
assert_output(/information\n/) {#game_i.input}
end
def test_case_q
assert_output(/quitter\n/) {#game_q.input}
end
def test_case_p
assert_output(/player play\n/) {#game_p.input}
end
end
Running the tests...
$ ruby test_game.rb
#Run options: --seed 55321
## Running:
#...
#Finished in 0.002367s, 1267.6099 runs/s, 2535.2197 assertions/s.
#3 runs, 6 assertions, 0 failures, 0 errors, 0 skips
You have to test each case branch. Via RSpec it would work that way:
describe Game do
subject { Game }
describe '#input' do
expect_any_instance_of(Game).to receive(:puts).with('information')
Game.new('i').input
expect_any_instance_of(Game).to receive(:puts).with('quitter')
Game.new('q').input
expect_any_instance_of(Game).to receive(:puts).with('player play')
Game.new('p').input
end
end
However due the fact that puts is ugly to test, you should refactor your code to something like that:
require 'pry'
require_relative 'messages'
class Game
attr_reader :user_answer
def initialize(user_answer = gets.chomp.downcase)
#user_answer = user_answer
end
def input
case user_answer
when "i"
"information"
when "q"
"quitter"
when "p"
"player play"
end
end
def print_input
puts input
end
end
Then you can test with RSpec via:
describe Game do
subject { Game }
describe '#print_input' do
expect_any_instance_of(Game).to receive(:puts).with('quitter')
Game.new('q').print_input
end
describe '#input' do
expect(Game.new('i').input).to eq('information')
expect(Game.new('q').input).to eq('quitter')
expect(Game.new('i').input).to eq('player play')
expect(Game.new('x').input).to eq(nil)
end
end

How can I test logger-messages with MiniTest?

I have an application and I want to test if I get correct
messages from my logger.
A short example (you may switch between log4r and logger):
gem 'minitest'
require 'minitest/autorun'
require 'log4r'
#~ require 'logger'
class Testlog < MiniTest::Test
def setup
if defined? Log4r
#log = Log4r::Logger.new('log')
#log.outputters << Log4r::StdoutOutputter.new('stdout', :level => Log4r::INFO)
else
#log = Logger.new(STDOUT)
#log.level = Logger::INFO
end
end
def test_silent
assert_silent{ #log.debug("hello world") }
assert_output(nil,nil){ #log.debug("Hello World") }
end
def test_output
#~ refute_silent{ #log.INFO("Hello") }#-> NoMethodError: undefined method `refute_silent'
assert_output("INFO log: Hello World\n",''){ #log.info("Hello World") }
end
end
But I get:
1) Failure:
Testlog#test_output [minitest_log4r.rb:27]:
In stdout.
Expected: "INFO log: Hello World\n"
Actual: ""
On my output screen I see the message.
I have similar results with Log4r::StderrOutputter and Log4r::Outputter.stdout.
So it seems it is send to the output screen, but it is not catched by minitest in STDOUT or STDERR.
Before I start to write a minitest-log4r-Gem:
Is there a possibility to test logger-output in minitest?
If not:
Any recommendations how to implement a minitest-log4r-Gem?
Examples what I could imagine:
define new outputter for minitest (Log4r::MinitestOutputter)
Mock the logger.
new assertions (add the new outputter as parameter?):
assert_message('INFO log: Hello World'){ #log.info("Hello World") }
assert_messages(:info => 1, :debug => 2){ #log.info("Hello World") } to count messages.
assert_info_messages('Hello World'){ #log.info("Hello World") }
assert_debug_messages('Hello World'){ #log.info("Hello World") }
You can set up a pipe, pass the writer from the pipe to the logger, and then use the reader from the pipe to test your assertions.
http://ruby-doc.org/core-2.1.0/IO.html#method-c-pipe
Something like:
require 'logger'
r, w = IO.pipe
log = Logger.new(w)
log.info "testing info log message"
output = r.gets
puts "Test passed: #{!!(/testing/ =~ output)}"
In meantime I created a minitest-logger-Gem
A code example how to use it:
require 'log4r'
require 'minitest-logger'
class Test_log4r < MiniTest::Test
def setup
#log = Log4r::Logger.new('log')
#log.level = Log4r::INFO
end
def test_output_1
assert_log(" INFO log: Hello World\n"){ #log.info("Hello World") }
end
def test_output_regex
assert_log(/Hello World/){ #log.info("Hello World") }
end
def test_silent
assert_silent_log(){
#log.debug("Hello World")
#~ #log.info("Hello World") #uncomment this to see a failure
}
refute_silent_log(){
#log.warn("Hello World") #comment this to see a failure
}
end
end
During the test a temporary outputter is added to the logger #log. After the test the outputter is removed again.
The gem supports log4r and logger.
#Puhlze answer is good. Just for non-blocking, check before hand if there is input available:
if IO.select([r], [], [], 0.01).nil?
Suppose we have this code here on a file called logger.rb:
require 'logger'
class Framework
attr_reader :logger
def initialize
#logger = Logger.new("/tmp/minitest.log")
end
end
class Custom
def initialize(framework)
#framework = framework
end
def error!
raise StandardError, 'Forced error'
rescue StandardError => e
#framework.logger.error "Error: #{e}"
end
end
And we need to test the logger error messages. We can use a stub method and a StringIO object:
require 'minitest'
require 'minitest/autorun'
require_relative 'logger.rb'
class LoggerTest < MiniTest::Test
def test_logger
framework = Framework.new
custom = Custom.new(framework)
io = StringIO.new
framework.stub :logger, Logger.new(io) do
custom.error!
assert_match(/Forced error/, io.string)
end
end
end
This way we don't need to override the framework logger, just stub it.

Ruby: Chatterbot can't load bot data

I'm picking up ruby language and get stuck at playing with the chatterbot i have developed. Similar issue has been asked here Click here , I did what they suggested to change the rescue in order to see the full message.But it doesn't seem right, I was running basic_client.rb at rubybot directory and fred.bot is also generated at that directory . Please see the error message below: Your help very be very much appreciated.
Snailwalkers-MacBook-Pro:~ snailwalker$ cd rubybot
Snailwalkers-MacBook-Pro:rubybot snailwalker$ ruby basic_client.rb
/Users/snailwalker/rubybot/bot.rb:12:in `rescue in initialize': Can't load bot data because: No such file or directory - bot_data (RuntimeError)
from /Users/snailwalker/rubybot/bot.rb:9:in `initialize'
from basic_client.rb:3:in `new'
from basic_client.rb:3:in `<main>'
basic_client.rb
require_relative 'bot.rb'
bot = Bot.new(:name => 'Fred', :data_file => 'fred.bot')
puts bot.greeting
while input = gets and input.chomp != 'end'
puts '>> ' + bot.response_to(input)
end
puts bot.farewell
bot.rb:
require 'yaml'
require './wordplay'
class Bot
attr_reader :name
def initialize(options)
#name = options[:name] || "Unnamed Bot"
begin
#data = YAML.load(File.read('bot_data'))
rescue => e
raise "Can't load bot data because: #{e}"
end
end
def greeting
random_response :greeting
end
def farewell
random_response :farewell
end
def response_to(input)
prepared_input = preprocess(input).downcase
sentence = best_sentence(prepared_input)
reversed_sentence = WordPlay.switch_pronouns(sentence)
responses = possible_responses(sentence)
responses[rand(responses.length)]
end
private
def possible_responses(sentence)
responses = []
#data[:responses].keys.each do |pattern|
next unless pattern.is_a?(String)
if sentence.match('\b' + pattern.gsub(/\*/, '') + '\b')
if pattern.include?('*')
responses << #data[:responses][pattern].collect do |phrase|
matching_section = sentence.sub(/^.*#{pattern}\s+/, '')
phrase.sub('*', WordPlay.switch_pronouns(matching_section))
end
else
responses << #data[:responses][pattern]
end
end
end
responses << #data[:responses][:default] if responses.empty?
responses.flatten
end
def preprocess(input)
perform_substitutions input
end
def perform_substitutions(input)
#data[:presubs].each {|s| input.gsub!(s[0], s[1])}
input
end
# select best_sentence by looking at longest sentence
def best_sentence(input)
hot_words = #data[:responses].keys.select do |k|
k.class == String && k =~ /^\w+$/
end
WordPlay.best_sentence(input.sentences, hot_words)
end
def random_response(key)
random_index = rand(#data[:responses][key].length)
#data[:responses][key][random_index].gsub(/\[name\]/, #name)
end
end
I'm assuming that you are trying to load the :data_file passed into Bot.new, but right now you are statically loading a bot_data file everytime. You never mentioned about bot_data in the question. So if I'm right it should be like this :
#data = YAML.load(File.read(options[:data_file]))
Instead of :
#data = YAML.load(File.read('bot_data'))

Testing STDOUT output in Rspec

I am trying to build a spec for this statement. It is easy with 'puts'
print "'#{#file}' doesn't exist: Create Empty File (y/n)?"
RSpec 3.0+
RSpec 3.0 added a new output matcher for this purpose:
expect { my_method }.to output("my message").to_stdout
expect { my_method }.to output("my error").to_stderr
Minitest
Minitest also has something called capture_io:
out, err = capture_io do
my_method
end
assert_equals "my message", out
assert_equals "my error", err
RSpec < 3.0 (and others)
For RSpec < 3.0 and other frameworks, you can use the following helper. This will allow you to capture whatever is sent to stdout and stderr, respectively:
require 'stringio'
def capture_stdout(&blk)
old = $stdout
$stdout = fake = StringIO.new
blk.call
fake.string
ensure
$stdout = old
end
def capture_stderr(&blk)
old = $stderr
$stderr = fake = StringIO.new
blk.call
fake.string
ensure
$stderr = old
end
Now, when you have a method that should print something to the console
def my_method
# ...
print "my message"
end
you can write a spec like this:
it 'should print "my message"' do
printed = capture_stdout do
my_method # do your actual method call
end
printed.should eq("my message")
end
If your goal is only to be able to test this method, I would do it like this:
class Executable
def initialize(outstream, instream, file)
#outstream, #instream, #file = outstream, instream, file
end
def prompt_create_file
#outstream.print "'#{#file}' doesn't exist: Create Empty File (y/n)?"
end
end
# when executing for real, you would do something like
# Executable.new $stdout, $stdin, ARGV[0]
# when testing, you would do
describe 'Executable' do
before { #input = '' }
let(:instream) { StringIO.new #input }
let(:outstream) { StringIO.new }
let(:filename) { File.expand_path '../testfile', __FILE__ }
let(:executable) { Executable.new outstream, instream, filename }
specify 'prompt_create_file prompts the user to create a new file' do
executable.prompt_create_file
outstream.string.should include "Create Empty File (y/n)"
end
end
However, I want to point out that I would not test a method like this directly. Instead, I'd test the code that uses it. I was talking with a potential apprentice yesterday, and he was doing something very similar, so I sat down with him, and we reimplemented a portion of the class, you can see that here.
I also have a blog that talks about this kind of thing.

Writing a DSL like Thor gem in Ruby?

I'm trying to figure out how the Thor gem creates a DSL like this (first example from their README)
class App < Thor # [1]
map "-L" => :list # [2]
desc "install APP_NAME", "install one of the available apps" # [3]
method_options :force => :boolean, :alias => :string # [4]
def install(name)
user_alias = options[:alias]
if options.force?
# do something
end
# other code
end
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
def list(search="")
# list everything
end
end
Specifically, how does it know which method to map the desc and method_options call to?
desc is pretty easy to implement, the trick is to use Module.method_added:
class DescMethods
def self.desc(m)
#last_message = m
end
def self.method_added(m)
puts "#{m} described as #{#last_message}"
end
end
any class that inherits from DescMethods will have a desc method like Thor. For each method a message will be printed with the method name and description. For example:
class Test < DescMethods
desc 'Hello world'
def test
end
end
when this class is defined the string "test described as Hello world" will be printed.

Resources