How do I write a rspec test for the following.
the value that gets passed in is this...
90A14F 1.4
def classname
def init
#input_colors = Array.new
end
def speak
puts "enter your line of color values"
result = STDIN.gets.chomp
new_result = result.gsub!(/([\s])+/,':')
#input_colors << new_result
end
end
How do I write an rspec 3.1 test for this speak method that test if gets.chomp is...90A14F 1.4
They will get an instance var #input_colors == ["90A14F:1.4"]
There are some issues in your example. I would change and fix it to something like this:
class ColorReader # with `class` and an upcase class name
def initialize # not `init`
#colors = [] # the use of `Array.new` is uncommon
end
def read
puts "enter your line of color values"
result = STDIN.gets.chomp
new_result = result.gsub!(/([\s])+/,':')
#colors << new_result
end
end
Then a test could look like this:
describe ColorReader do
describe '#read' do
let(:input) { "90A14F 1.4\n" }
subject(:color_reader) { ColorReader.new }
before do
allow(color_reader).to receive(:puts).and_return(nil)
allow(STDIN).to receive(:gets).and_return(input)
end
it 'writes to the console' do
color_reader.read
expect(color_reader).to have_received(:puts).
with("enter your line of color values").once
end
it 'reads from STDIN' do
color_reader.read
expect(STDIN).to have_received(:gets).once
end
it 'returns the sanitized input' do
expect(color_reader.read).to eq(['90A14F:1.4'])
end
end
end
Btw. I would prefer to explicitly test the new value of the #colors array (perhaps with attr_reader :colors) and not the implizit return value of the read method. If you have a reader for the #colors then the last spec can be changed to:
it 'adds the sanitized input to `colors`' do
expect {
color_reader.read
}.to change { color_reader.colors }.from([]).to(['90A14F:1.4'])
end
Related
Aim
My program uses an algorithm to make a board, line-by-line in an array of arrays.
The final array looks like this (a standard 3x3 board with a border):
[['-----'],['|...|'],['|...|'],['|...|'],['-----']]
Problem
The failing test is as follows:
Note: view_board puts #board
expect { board.view_board }.to output(['-----','|...|','|...|','|...|','-----']).to_stdout
Outputting:
`Failure/Error: expect { board.view_board }.to output(['-----','|...|','|...|','|...|','-----']).to_stdout
expected block to output ["-----", "|...|", "|...|", "|...|", "-----"] to stdout, but output "-----\n|...|\n|...|\n|...|\n-----\n"
Diff:`
What'd different?
I'm not sure if there's a space somewhere I've missed, if I change the final array to "-----\n", then I get:
Diff:
## -2,5 +2,5 ##
|...|
|...|
|...|
------\n
+-----
Edit:
Board-Generating Code
class Board
BOARD_ROW = '-'
BOARD_COLUMM = '|'
BEGINNING_AND_END_LENGTH = 2
def initialize(board_size = 3)
#board = []
#board_top_and_bottom = []
#board_middle = []
#board_size = board_size
end
def go
set_board
end
def set_board
set_board_top_and_bottom
set_board_middle
assemble_board
view_board
end
def set_board_top_and_bottom
(#board_size + BEGINNING_AND_END_LENGTH).times do
#board_top_and_bottom.push(BOARD_ROW)
end
#board_top_and_bottom = [#board_top_and_bottom.join]
end
def set_board_middle
add_board_edge
add_board_spaces
add_board_edge
#board_middle = [#board_middle.join]
end
def add_board_spaces
#board_size.times do
#board_middle.push('.')
end
end
def add_board_edge
#board_middle << BOARD_COLUMM
end
def assemble_board
#board << #board_top_and_bottom
#board_size.times do
#board << #board_middle
end
#board << #board_top_and_bottom
end
def view_board
puts #board
end
end
From the docs of puts:
puts(obj, ...) → nil
Writes the given object(s) to ios. Writes a newline after any that do not already end with a newline sequence. [...]
That means when your call puts ['foo', 'bar'] then Ruby will actually output "foo\nbar"
To make your spec pass change the expectation to:
expect { board.view_board }.to output("-----\n|...|\n|...|\n|...|\n-----\n").to_stdout
Undefined method for nil:Nilclass
In a class, a method counts the number of words in a paragraph.An error occurs when a method is called(1). I can’t understand how to pass the argument methods using send.
If I remove the class and put the def calc_1(paragraph) method into the loop, then everything works, I start calling the select method. It turns out he does not see my books variable with text, when there is a class.
#books = "You can use this knowledge to create small tools that might help."
class Filecalculation
def select
loop do
puts "# Will we search : сounting words in text File(1)".cyan
print "\n>>>>>> "
input = gets.chomp
search_method = "calc_#{input}".to_sym
if (respond_to?(search_method))
contents = send(search_method, #books)
end
end
end
def calc_1 paragraph
word_count = paragraph.split.length
puts "#{word_count} words"
end
end
Filecalculation.new.select
If replaced by search_method = "calc_#{input}".to_sym also works.
Helped add def initialize #books end.
Instead of contents = send (search_method, #books) you can use send (search_method, #books).
require "colorize"
class Filecalculation
def initialize
#books = "You can use this knowledge to create small tools that might help you."
end
def calc_1 paragraph
word_count = paragraph.strip.squeeze(' ').count(' ') + 1
puts "#{word_count} words"
end
def select
loop do
puts "# Will we search : Calculation_lines paragraph(1)".cyan
print "\n>>>>>> ".yellow
input = gets.chomp
search_method = "calc_#{input}" #.to_sym
if (respond_to?(search_method))
contents = send(search_method, #books)
else
puts "exit "
exit
end
end
end
end
Filecalculation.new.select
I have a class that does calculations. Most of the times it is used in the code to output a single value:
Use: value = Calculator.new(1234).output
This is an example of the class definition:
class Calculator
def initialize(parameter_1)
#parameter_1 = parameter_1
end
def output
op_1_result = operation_1(#parameter_1)
op_2_result = operation_2(op_1_result)
operation_3(op_2_result)
end
def operation_1(param)
...
end
But sometimes the user has to print a report of the calculation, showing many of the variables from inside the calculations.
The first solution I implemented was to pass a parameter at initialization telling the class that it should save some internal variables for the report, like this:
class Calculator
attr_reader :report
def initialize(parameter_1, reporting=false)
#parameter_1 = parameter_1
#reporting = reporting
#report = {}
end
def output
op_1_result = operation_1(#parameter_1)
#report[:op_1_result] = op_1_result if #reporting
op_2_result = operation_2(op_1_result)
#report[:op_2_result] = op_2_result if #reporting
operation_3(op_2_result)
end
def operation_1(param)
...
end
Then, when I want to get those intermediate variables, I would:
calculator = Calculator.new(1234, true) # reporting activated
report = calculator.report
report[:op_1_result] # one of the intermediate variables of the calculation
Does this break the single responsibility principle, as the class is now calculating the value and reporting at the same time?
Is there a better way to do this, a design pattern where I could have a fast calculation of the output result where it is needed and show the report where needed without all those ifs?
Any light on this and comments will be really appreciated!
Obs (another discussion): I've read that a more functional approach to just outputting a value would be a great thing. But that kept me wondering about how to show those internal intermediate values when needed. How do more functional programmers would do it...?
I guess "builder pattern" is suitable and "report pad" should be injected from outside.
class Calculator
def initialize(*parameters)
#parameters = parameters
end
def report_to(report_pad)
#report_pad = report_pad
self
end
def output()
ret = #parameters[0].to_i + #parameters[1].to_i
report('Did p0 + p1')
ret
end
private
def report(message)
#report_pad << "\n".prepend(message) if #report_pad.respond_to? '<<'
end
end
####
reports = ""
result = Calculator
.new(1, 2)
.report_to(reports)
.output()
puts result, reports
Why don't you just make all intermediate results public methods and chain the results in the final output?
Perhaps something like this:
class Calculator
def initialize(parameter)
#parameter = parameter
end
def output
op_3_result
end
def op_1_result
#op_1_result ||= operation_1(parameter)
end
def op_2_result
#op_2_result ||= operation2(op_1_result)
end
def op_3_result
#op_3_result ||= operation3(op_2_result)
end
private
def operation1(arg)
# ...
end
def operation2(arg)
# ...
end
def operation3(arg)
# ...
end
attr_reader :parameter
end
That would allow you to call whatever you need on the same instance:
calculator = Calculator.new(1234)
calculator.output #=> the final result
calculator.op_2_result #=> the intermediate result of step 2
You could use a different pattern with Report being its own class and allow it to just pass through when reporting is turned off. Here is a simple example:
class Calculator
attr_reader :report
def initialize(parameter_1, reporting=false)
#parameter_1 = parameter_1
#report = Report.new(reporting)
end
def output
op1 = operation_1(report.capture(:param1,#parameter_1))
report.capture(:op1,op1)
op2 = report.capture(:op2) { operation_2(op1) }
operation_3(op2)
end
def operation_1(param);
param + 7
end
def operation_2(param);
param - 3
end
def operation_3(param);
param * 2
end
end
class Report
attr_reader :reporting, :reportables
def initialize(reporting)
#reporting = reporting
#reportables = {}
end
def capture(key, val=nil,&block)
warn "Block supercedes value" if val && block_given?
result = block_given? ? block.call : val
#reportables[key] = result if reporting
result
end
def [](key)
return 'No Reporting' unless reporting
#reportables[key]
end
end
Then you can use like so
c = Calculator.new(12)
c.output
#=> 32
c.report[:op1]
#=> "No Reporting"
c = Calculator.new(12, true)
c.output
#=> 32
c.report[:op1]
#=> 19
c.report[:param1]
#=> 12
This way each step can use a block for more complicated items where the result should be captured or just pass a value if you choose and intermediate steps like operation_3 (in this case) that do not need to be "captured" can just flow through.
If reporting is off then everything just flows through and the captures are ignored.
Your #output method could also look like this (no intermediate locals at all although it does hurt the readability a bit)
def output
operation_3 (
report.capture(:op2,
operation_2(
report.capture(:op1) do
operation_1(report.capture(:param1,#parameter_1))
end
)
)
)
end
You can use dependency injection like this:
class Calculator
def initialize(parameter_1, reporter = nil)
#parameter_1 = parameter_1
#reporter = reporter
end
def output
op_1_result = call_with_reporting :operation_1, #parameter_1
op_2_result = call_with_reporting :operation_2, op_1_result
operation_3(op_2_result)
end
def operation_1(param)
...
end
def call_with_reporting(operation, *args)
result = self.send(operation, *args)
#reporter.report(operation, result) if #reporter
result
end
end
class ConsoleReporter
def initialize
#results = {}
end
def report(operation, result)
#results[operation] = result
end
def run_report
puts #operations
end
end
Now you can use Calculator like this:
reporter = ConsoleReporter.new
Calculator.new(12, reporter).output
reporter.run_report
Later you can use Calculator with other format reporter (like ToFileReporter)
I'm trying to build a relatively simple app in ruby. However I am unable to get my object to return anything other than 0 when puts obj.to_s is called on it. I understand the quality of the code may be poor (and wouldn't mind any hints).
Help please!
class Docpart
def Docpart.new(inputAsString,typeAsInteger)
#value = inputAsString
#type = typeAsInteger.to_i # 0 = title, 1 = text, 2 = equation (can't be done yet), 3 = table
end
def Docpart.to_s
return "Type is #{#type}, value is #{#value}"
end
end
module Tests
def Tests.test1()
filetree = Array.new(0)
filetree.push( Docpart.new("Title",0))
filetree.each{|obj| puts obj.to_s}
return filetree[0]
end
end
puts Tests.test1.to_s
gets.chomp
Because you defined class method to_s not instance one. Also writing constructor in Ruby is a little different. You need to write this that way:
class Docpart
def initialize(inputAsString,typeAsInteger)
#value = inputAsString
#type = typeAsInteger.to_i # 0 = title, 1 = text, 2 = equation (can't be done yet), 3 = table
end
def to_s
"Type is #{#type}, value is #{#value}"
end
end
module Tests
def self.test1
filetree = []
filetree << Docpart.new("Title",0)
filetree.each{ |obj| puts obj.to_s }
filetree[0]
end
end
puts Tests.test1.to_s
gets.chomp
PS Read any book about Ruby and any styleguide like Githubbers or bbatsov one.
class DobbsyKretts
def initialize
#Receive idea
puts "Enter an idea, a secret or anything else you want to secretize; hit enter to stop typing and save the file"
(#idea = gets).reverse.upcase
#Filename and saving - to encrypt the file
puts "Enter the file name you'd like to have this saved as; Type PLAN at the beginning for plans and REM for reminders"
(#file_name = gets.chomp.upcase)
File::open("DobbsyKrett-"+ #file_name + ".txt", "w") do |f|
f << #idea
end
end
def unzip
puts "Do you want to withdraw PLAN or REM"
response = gets.chomp.upcase!
puts "Invalid" if !["PLAN","REM"].include?(response)
file_contents = nil
Dir['DobbsyKrett-'+response+"*.txt"].each do |file_nom|
file_contents = File.read(file_nom)
end
puts file_contents
end
end
somethingsomething1 = DobbsyKretts.new
somethingsomething1.unzip
def unzip
puts "Do you want to withdraw PLAN or REM"
#response = gets.strip
if #response.downcase != "plan" and #response.downcase != "rem"
puts "Invalid" end
Dir["DobbsyKrett-"+#response+".txt"].each do |file_nom|
#value = file.read(file_nom)
end
puts #value
end
end
The function gets will return a string with the line-ending character at the end which is not what you expected. To remove it, use the chomp function:
#response = gets.chomp
It is okay for a method (e.g. unzip) to create new instance variables (e.g. #valueholder). In general it's always better for your variables to have the smallest possible scope, so unless you need to read valueholder later, you should just use a local variable (remove the # from the name):
Dir["DobbsyKrett-"+#response+".txt"].each do |file_nom|
valueholder = File.read(file_nom)
end
puts valueholder
Also, valueholder is a terrible name for a variable but if you made it a local variable that could be excused.
Also, your block startings/endings are mismatched. Here's a fixed version of your function that shouldn't result in syntax errors:
def unzip
puts "Do you want to withdraw PLAN or REM"
response = gets.chomp.downcase
if !["plan","rem"].include? response
puts "Invalid"
else
Dir["DobbsyKrett-#{response}.txt"].each do |file_nom|
valueholder = file.read(file_nom)
end
puts valueholder
end
end
Edit: You should capitalize File to correctly call File.read.