Rspec模型测试平均评级方法

时间:2013-04-28 18:44:02

标签: ruby-on-rails ruby rspec tdd factory-bot

我正在尝试为以下方法编写测试:

def average_rating
  reviews = self.reviews
  review_sum = reviews.inject(0) { |sum, review| sum += review.rating }
  avg_rating = (review_sum / reviews.count).to_i unless review_sum == 0
end

这是我到目前为止所拥有的:

describe "#average_rating" do
it "should be nil when there are no reviews" do
  api_without_reviews = Api.new name: 'Fake API', description: 'It does something', category: 'Fake category', url: 'http://www.example.com'
  api_without_reviews.average_rating.should be_nil
end
it "should calculate average rating properly" do
  api_with_reviews = Api.new name: 'Fake API', description: 'It does something', category: 'Fake category', url: 'http://www.example.com'
  api_with_reviews.save
  api_with_reviews.reviews.create rating: Random.rand(1..5), thoughts: 'blah'
  api_with_reviews.reviews.create rating: Random.rand(1..5), thoughts: 'blah'
  average = api_with_reviews.reviews.inject(0) { |sum, review| sum += review.rating } / api_with_reviews.reviews.count
  api_with_reviews.average_rating.should eq average
end

如何测试review_sum变量是否正确计算总和,或者方法是否已经过全面测试?我是rspec的新人,所以感谢任何帮助!

3 个答案:

答案 0 :(得分:2)

直接回答我建议你不要:

  • 生成评论评级的随机值
  • 直接复制方法代码以验证它。你不能为自己的论文打分,不是吗?

相反:

  • 使用手动创建的评分并手动计算平均值
  • 验证方法是否可以输出您计算的内容。

进一步改进

  • 使用FactoryGirl创建测试对象。<​​/ li>
  • 干掉代码

我的版本(假设已安装FactoryGirl)

describe "API" do
  describe "average_rating" do
    # Use "before" but not "let" because this var must be clean in every example
    before { @api = FactoryGirl.create(:api) }

    it "should be nil when there are no reviews" do
      @api.average_rating.should be_nil
    end

    it "should calculate average rating properly" do
      [1, 5].each do |r|
        FactoryGirl.create(:review, rating: r, api: @api)
      end 
      @api.average_rating.should eq(3)
    end
  end
end

# spec/factories.rb
FactoryGirl.define do
  factory :api do
    name 'Fake API'
    description 'It does something'
    category 'Fake category'
    url 'http://www.example.com'
  end

  factory :review do
    rating rand(1..5)
    thoughts "blah"
    api   #Association: belongs_to API
  end
end

答案 1 :(得分:0)

如果你不想安装和设置factorygirl(我建议你将来使用它,它在测试和开发控制台中都非常有用,它可以大大简化创建很多对象必需的属性),这里有一些代码。这也使用了新的rspec语法,其中'expect'比'should'更受青睐。

describe "API" do
  describe "Method: average_rating" do
    before(:each) do
      @api = Api.create(:attributes => "some value")
    end

    it "is nil when there are no reviews" do
      expect( @api.average_rating ).to be_nil
    end

    it "calculates average rating properly" do 
      ratings_sum = 0
      5.times do |rating_value|
        @api.ratings << Rating.new(:rating => rating_value, :thoughts => "lorem ipsum")
        ratings_sum += rating_value
      end
      ratings_average = ratings_sum / @api.ratings.count
      expect( @api.average_rating ).to eq( ratings_average )
    end
  end
end

答案 2 :(得分:0)

我认为没有必要使用FactoryGirl进行此测试。您并没有真正测试数据库的任何内容。您只想为对象设置状态,然后测试方法的结果。

根据您设置测试的方式,我认为您走在正确的轨道上。使用RSpec 2.13和Ruby 1.9.3,这就是我编写测试的方法。

describe Api do

  subject(:api) { Api.new name: 'API Under Test', description: 'Testing' }

  describe "requesting the average rating" do
    context "with no reviews" do
      it { expect(api.average_rating).to be_nil }
    end

    context "with one or more reviews" do
      it "is the standard average of the review ratings rounded down" do
        api.reviews = [
          Review.new(rating: 1, thoughts: 'test review 1'),
          Review.new(rating: 2, thoughts: 'test review 2'),
          Review.new(rating: 5, thoughts: 'test review 3'),
        ]

        expect(api.average_rating).to eq 2
      end
    end
  end

end

一些跟进点:

  • 您不必使用内部context,但是,我这样做是为了在设置上提供更多语义(而不是您希望测试证明的内容); context ==设置,it ==测试结果
  • 从RSpec 2.11开始,您可以使用命名主题而不仅仅是let。这里的优点是您提供了被测对象
  • 的语义线索
  • let并且命名为subject为您提供了一种定义在测试中使用的备忘变量的方法
  • 我远离@api版本的实例变量,因为它可以在以后更改时提供更大的灵活性,而无需更新所有测试(请参阅barewords
  • 有关某些RSpec约定的详细信息,请参阅http://betterspecs.org/