简体   繁体   中英

RSpec: DRY way to test a set of values

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM