I have a vote
model, which has a class method called score
. Basically, I created a mathematical equation in a spreadsheet, and am attempting to reproduce this in ruby. However, my first go isn't working, so I really need to start adding a ton more tests.
The way I'd like to go about testing this is to take a stack of input and output values from my spreadsheet and test them each. So basically, the tests could be construed to look like this:
inputs = [a,b,c] ... score.should == x
inputs = [a,b,c,d] ... score.should == y
inputs = [c,d] .... score.should == z
However, the shortest way I've actually found to write this in RSpec is giving an example for each case, approximately like this (simplified example, but should give you the idea):
it "should have a score of X" do
test_object = Votable.new(...)
@user1.vote.create(:value=>##, :votable=>test_object)
@user2.vote.create(:value=>##, :votable=>test_object)
@user3.vote.create(:value=>##, :votable=>test_object)
test_object.votes.score.should == X
end
So, the above works, but its a load of text for each example case, and to iron out the kinks and provide good test coverage I'd like to run about 20 or so test cases.
So, seriously, there must be a simpler way to set this up one time and then test a bunch of possible input/output combinations, right? Can anyone suggest a DRY way to do this kind of test in RSpec?
Thanks!
Yeah, you could do the following meta-programming to run a series of test that all follow the same format:
results = { x: ['a', 'b', 'c'], y: ['a','b','c','d'] }
results.each do |score, values|
it "should have a score of #{score}" do
test_object = Votable.new(...)
values.each do |value|
User.create(...).vote.create(value: value, votable: test_object)
end
test_object.votes.score.should == score
end
end
@Pan Thomakos:
Your answer inspired me (so I accepted it!) but I actually created something a little different inspired by your suggestion above. I'm so happy with it I thought I'd share it in case it benefits anyone else.
Previously my model had this method:
def self.score
dd = where( :value => -2 ).count.to_f
d = where( :value => -1 ).count.to_f
u = where( :value => 1 ).count.to_f
uu = where( :value => 2 ).count.to_f
tot = dd + d + u + uu
score = (((-5*dd)+(-2*d)+(2*u)+(5*uu))/(tot+4))*20
score.round(2)
end
This worked, but it requires counting votes from the database, seeing the count of votes with each possible value (-2, -1, +1, +2) and then computing the score from these counts.
Since what I needed to test was not ActiveRecord's ability to find and count query results, but my algorithm for turning those counts into a score, I split this into two methods, like so:
def self.score
dd = where( :value => -2 ).count
d = where( :value => -1 ).count
u = where( :value => 1 ).count
uu = where( :value => 2 ).count
self.compute_score(dd,d,u,uu)
end
def self.compute_score(dd, d, u, uu)
tot = [dd,d,u,uu].sum.to_f
score = [-5*dd, -2*d, 2*u, 5*uu].sum / [tot,4].sum*20.0
score.round(2)
end
So now I can just test the compute_score
method without needing to create a bunch of fake users and fake votes to test the algorithm. My test now looks like:
describe "score computation" do
def test_score(a,b,c,d,e)
Vote.compute_score(a,b,c,d).should == e
end
it "should be correct" do
test_score(1,0,0,0,-20.0)
test_score(0,1,0,0,-8.0)
test_score(0,0,1,0,8.0)
test_score(0,0,0,1,20.0)
test_score(0,0,10,100,91.23)
test_score(0,6,60,600,92.78)
test_score(0,20,200,2000,93.17)
end
end
In my opinion this is super legible, and if I ask RSpec for the formatted output it reads well enough for what it's testing.
Hopefully this technique will be useful for others!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.