So I'm trying to write some code to make an RSPEC test pass for Ruby. But I'm having some trouble even getting the first test to pass. I think if I can have a little help with this, I'll be able to get the rest. But I can post the rest of the tests if that makes it easier to offer advice.
So it's constructing a fahrenheit/celsius converter, but using objects and classes instead of just defining a couple of methods to do the conversions.
The first part looks like this
require "temperature"
describe Temperature do
describe "can be constructed with an options hash" do
describe "in degrees fahrenheit" do
it "at 50 degrees" do
Temperature.new(:f => 50).in_fahrenheit.should == 50
end
One of the hints in the instructions says that the Temperature object constructer should accept an options hash with either a :celsius or :fahrenheit entry.
Any help or hints would be greatly appreciated. I've been stuck on this test for the last few weeks. Thanks in advance.
I think your Temperature class needs some work. Why not have a 'scale' attribute that can set the base value of your Temperature object?
Here's a modified version of what you've posted:
class Temperature
attr_accessor :value, :scale
def initialize(value, options={})
#value = value
if options.has_key?(:scale)
#scale = options[:scale]
else
#scale = :c
end
end
def in_fahrenheit()
if #scale == :c
( #value * 9.0/5.0 ) + 32.0
else
#value
end
end
end
There's no need to create a new Temperature object when you call #in_fahrenheit. You can make your current object convert a number (stored in an attribute) into fahrenheit. You can add the temperature information when you create your object:
t1=Temperature.new(68, :scale =>:f)
t2=Temperature.new(0)
2.0.0dev :199 > t1.in_fahrenheit => 68
2.0.0dev :200 > t2.in_fahrenheit => 32.0
Related
I am building a sinatra web app with a sequel database backend. The primary tasks of this app is collecting status messages from different robots, store them in a database and provide various methods to view them. A common denominator in these messages is, that they provide a WGS84 position in lat/lon.
Now I want to provide various filters for querying messages based on their positions, but I want to write these filters only once, test them only once but re-use them in all model-classes with a lat/lon entry.
To boil it down to a very simple example:
Sequel.migration do
up do
create_table(:auvmessages) do
primary_key :id
Float :lat
Float :lon
String :message
end
create_table(:asvmessages) do
primary_key :id
Float :lat
Float :lon
Integer :chargestate
end
end
end
class Auvessage < Sequel::Model
dataset_module do
def north_of(lat)
self.where{ latitude > lat}
end
end
end
class Asvessage < Sequel::Model
dataset_module do
def north_of(lat)
self.where{ latitude > lat}
end
end
end
In both model classes have north_of(lat) to filter for messages which originate north of a given latitude. This function is fairly simple and you can easily repeat it two or three times, but what about more complex cases?
I have played around a bit with modules outside of dataset_module but nothing seem to be right.
Is there a preferred way how to re-use filters over different models? I have searched a lot, but didn't find any satisfying answer.
Edit:
To make my question a bit more precise: I want to move all functions like north_of(lat) (there are a lot more) into a service class. What I want to know now, is the best way to integrate that service class into a sequel-model:
"Just" include it?
Extend dataset_module, and if so, how?
Writing a dataset-plugin?
...
You can pass an existing module to dataset_module:
module NorthOf
def north_of(lat)
where{latitude > lat}
end
end
Auvessage.dataset_module NorthOf
Asvessage.dataset_module NorthOf
As a followup: I have taken #jeremy-evans answer and extended it by a parametrisation scheme for modules. So from now on I can test my filters by mocking and my model classes have just a list of includes in their dataset_module.
I like it.
As explanation my slightly modified example:
Sequel.migration do
up do
create_table(:auvmessages) do
primary_key :id
Float :lat
Float :lon
String :message
end
create_table(:asvmessages) do
primary_key :id
Float :gps_lat
Float :gps_lon
Integer :chargestate
end
end
end
module GPSFilter
def self.create(lat_name, lon_name)
Module.new do
include GPS
define_method :lat_col_name do
lat_name
end
define_method :lon_col_name do
lon_name
end
end
end
def north_of(lat)
where( "#{lat_col_name} > #{lat}" )
end
##### default parameters #####
def lon_col_name
"lon"
end
def lat_col_name
"lat"
end
end
class Auvmessage < Sequel::Model
dataset_module do
include GPSFilter
end
end
class Asvmessage < Sequel::Model
dataset_module do
include GPSFilter.create :gps_lat, :gps_lon
end
end
Here is a link to Uncle Bob's Screaming Architecture blog post which might be of help.
Now, answering your question, it seems that north_of, as well as many other methods, are actually part of your domain logic. This logic should not go in persistence abstractions, or controllers, or views, etc.
Design, build and write tests for the set of objects that solves your problem in the language of the domain of your problem. Then, you'll have at hand a rich set of functionality that you can simply use on Models, Controllers, CLIs, etc.
I usually put my service objects in a lib/ directory and write simple unit tests, without any of the persistence boilerplate that sets up test databases. They usually run very fast as well.
After reading this question I really do not like the answer.
Rails / RSpec: How to test #initialize method?
Maybe I am having a third scenario. This is what I have now, inspired by second code from that answer.
# Picture is collection of SinglePictures with same name and filename,
# but different dimensions
class Picture
attr_accessor :name, :filename
attr_reader :single_pics, :largest_width
def initialize(name, filename, dimensions=nil)
#largest_width = 0
#single_pics = {}
add_single_pics(dimensions) if dimensions
end
def add_single_pics(max_dimension)
# logic
end
end
describe '#initialize' do
it 'should not call add_single_pics if dimensions is not given' do
subject = Picture.new('Test Picture', 'Test-Picture')
expect(subject.largest_width).to eq 0
end
it 'should call add_single_pics if dimensions are given' do
subject = Picture.new('Test Picture', 'Test-Picture', 1920)
expect(subject.largest_width).to eq 1920
end
end
I really don't like this because I am testing the functionality of add_single_pics in #initialize tests. I would like to write somehow this in spec:
expect(subject).not_to have_received(:add_single_pics)
expect(subject).to have_received(:add_single_pics)
But I get
Expected to have received add_single_pics, but that object is not a spy
or method has not been stubbed.
Can I fix this somehow?
Spies are an alternate type of test double that support this pattern
by allowing you to expect that a message has been received after the
fact, using have_received.
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/basics/spies
Only spy object can store the method calls. To test your real class in the way that you want, you have to use expect_any_instance_of statement before the class will be initialized:
expect_any_instance_of(Picture).to receive(:add_single_pics)
Picture.new('Test Picture', 'Test-Picture')
In this case your add_single_pics method will be called, but its logic will not be run, if you need to run it you need to call the and_call_original method on the matcher:
expect_any_instance_of(Picture).to receive(:add_single_pics).and_call_original
This a simplified version of what I am trying to solve:
In Ruby 2.0.0, Rails 4.0.0, Activerecord
Segment has_many Sales
Sale.find(1).bid = 1
Sale.find(1).ask = 2
Sale.find(2).bid = 10
Sale.find(2).ask = 20
etc
When I write this method:
class Segment
def add_stuff(param)
sales.map{ |s| s.param }.inject(:+)
end
end
Question: How to pass in bid or ask as param and interpolate that param properly within the block? Thanks.
Never use map on active record association, when you can use pluck or other querying method like, for example sum:
def add_staff(param)
sales.sum(param)
end
Just for the completeness, here is how to do it with map - note however that map is much slower and error prone than querying methods:
def add_staff(param)
sales.map {|s| s[param]} # if param is name of the column on sales model.
end
Or more general, where param is name of association or custom (non-column) method
def add_staff(param)
sales.map(¶m) # Or map {|s| s.send(param) }
end
I've been stuck on this Learnstreet lesson for a day now. The exercise prompts:
Can you now implement a method called transfer! which takes two parameters, amount and other_account. The method should withdraw the specified amount from the current object and deposit it into other_account object.
The code in the editor goes as follows:
class BankAccount
attr_accessor :name, :balance, :address
def initialize(name, balance, address)
#name = name
#balance = balance
#address = address
end
def withdraw!(amount)
if #balance - amount > 0
#balance = #balance - amount
end
#balance
end
def deposit!(amount)
#balance += amount
end
# your code here
end
alices_account = BankAccount.new("Alice Cooper", 2500, "456 University Avenue")
bobs_account = BankAccount.new("Bob Ventura", 2100, "3500 Fox Street")
I know that you need to set up a method with def transfer!(amount, other_account). However I do not know what to put in the bottom after alices_account and bobs_account.
You'd call transfer! on one of the objects, passing in the other, e.g.,
bobs_account.transfer!(500, alices_account)
You're just calling a method on an instance, like "foo".size or [1, 2, 3].each etc. The only difference is that you've created the method you're calling.
I know that you need to set up a method with def transfer!(amount, other_account).
So basically you have to create BankAccount#transfer! that withdraw some money from the object that calls it and deposit the sum into the "other" BankAccount object.
The solution is pretty trivial since you have the BankAccount#withdraw! and BankAccount#deposit! already set up:
def transfer!(amount, other_account)
withdraw! amount
other_account.deposit! amount
end
However I do not know what to put in the bottom after alices_account and bobs_account.
The exercise doesn't require you to do anything with the latter. If you were supposed to do something you would need to know the amount of "money" to transfer from alices_account to bobs_account an viceversa and then go with:
# assuming x to be the amount to transfer
alices_account.transfer! x, bobs_account
or:
# assuming x to be the amount to transfer
bobs_account.transfer! x, alices_account
Ok now. I've spent an hour to complete all the 10 course before that one and this is what I discovered. At some point you get to write the last two lines of your code.
Then a weird thing happens. The code generated by the exercise contains a . To near the end which is obviously a syntax error. By removing that line and adding the method I provided above you get to pass the test.
What's the best way to set Time.now for the purpose of testing time-sensitive methods in a unit test?
I really like the Timecop library. You can do time warps in block form (just like time-warp):
Timecop.travel(6.days.ago) do
#model = TimeSensitiveMode.new
end
assert #model.times_up!
(Yes, you can nest block-form time travel.)
You can also do declarative time travel:
class MyTest < Test::Unit::TestCase
def setup
Timecop.travel(...)
end
def teardown
Timecop.return
end
end
I have some cucumber helpers for Timecop here. They let you do things like:
Given it is currently January 24, 2008
And I go to the new post page
And I fill in "title" with "An old post"
And I fill in "body" with "..."
And I press "Submit"
And we jump in our Delorean and return to the present
When I go to the home page
I should not see "An old post"
Personally I prefer to make the clock injectable, like so:
def hello(clock=Time)
puts "the time is now: #{clock.now}"
end
Or:
class MyClass
attr_writer :clock
def initialize
#clock = Time
end
def hello
puts "the time is now: #{#clock.now}"
end
end
However, many prefer to use a mocking/stubbing library. In RSpec/flexmock you can use:
Time.stub!(:now).and_return(Time.mktime(1970,1,1))
Or in Mocha:
Time.stubs(:now).returns(Time.mktime(1970,1,1))
I'm using RSpec and I did this: Time.stub!(:now).and_return(2.days.ago) before I call Time.now. In that way I'm able to control the time I used for that particular test case
Using Rspec 3.2, the only simple way I found to fake Time.now return value is :
now = Time.parse("1969-07-20 20:17:40")
allow(Time).to receive(:now) { now }
Now Time.now will always return the date of Apollo 11 landing on the moon.
Source: https://www.relishapp.com/rspec/rspec-mocks/docs
Do the time-warp
time-warp is a library that does what you want. It gives you a method that takes a time and a block and anything that happens in the block uses the faked time.
pretend_now_is(2000,"jan",1,0) do
Time.now
end
Don't forget that Time is merely a constant that refers to a class object. If you're willing to cause a warning, you can always do
real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
If you have ActiveSupport included, you could use:
travel_to Time.zone.parse('2010-07-05 08:00')
http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html
Also see this question where I put this comment as well.
Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.
Had the same issue, I had to fake time for a spec for a specific day and time just did that:
Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))
this will give you:
2014-10-22 05:35:28 -0700
This kind of works and allows for nesting:
class Time
class << self
attr_accessor :stack, :depth
end
def self.warp(time)
Time.stack ||= []
Time.depth ||= -1
Time.depth += 1
Time.stack.push time
if Time.depth == 0
class << self
alias_method :real_now, :now
alias_method :real_new, :new
define_method :now do
stack[depth]
end
define_method :new do
now
end
end
end
yield
Time.depth -= 1
Time.stack.pop
class << self
if Time.depth < 0
alias_method :new, :real_new
alias_method :now, :real_now
remove_method :real_new
remove_method :real_now
end
end
end
end
It could be slightly improved by undefing the stack and depth accessors at the end
Usage:
time1 = 2.days.ago
time2 = 5.months.ago
Time.warp(time1) do
Time.real_now.should_not == Time.now
Time.now.should == time1
Time.warp(time2) do
Time.now.should == time2
end
Time.now.should == time1
end
Time.now.should_not == time1
Time.now.should_not be_nil
Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.
i just have this in my test file:
def time_right_now
current_time = Time.parse("07/09/10 14:20")
current_time = convert_time_to_utc(current_date)
return current_time
end
and in my Time_helper.rb file i have a
def time_right_now
current_time= Time.new
return current_time
end
so when testing the time_right_now is overwritten to use what ever time you want it to be.
I allways extract Time.now into a separate method that I turn into attr_accessor in the mock.
The recently-released Test::Redef makes this and other fakery easy, even without restructuring the code in a dependency-injection style (especially helpful if you're using other peoples' code.)
fake_time = Time.at(12345) # ~3:30pm UTC Jan 1 1970
Test::Redef.rd 'Time.now' => proc { fake_time } do
assert_equal 12345, Time.now.to_i
end
However, be careful of other ways to obtain time that this will not fake out (Date.new, a compiled extension that makes its own system call, interfaces to things like external database servers which know current timestamps, etc.) It sounds like the Timecop library above might overcome these limitations.
Other great uses include testing things like "what happens when I'm trying to use this friendly http client but it decides to raise this an exception instead of returning me a string?" without actually setting up the network conditions which lead to that exception (which may be tricky). It also lets you check the arguments to redef'd functions.
My own solution https://github.com/igorkasyanchuk/rails_time_travel - a gem with UI, so you don't need to hardcode any datetime in the code. Just change it from the UI.
It might be also very useful for you QA's team, or testing app on the staging.