【问题标题】:Rspec model tests for average rating method平均评分法的 Rspec 模型测试
【发布时间】:2013-04-28 18:44:01
【问题描述】:

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

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 的新手,因此感谢您的帮助!

【问题讨论】:

  • 恕我直言,您应该避免在测试中随机化。
  • 在你的方法中似乎不需要这条线:reviews = self.reviews
  • 另一个小技巧:review_sum = reviews.map(:rating).reduce(:+)
  • @AaronK 谢谢,重构不错!

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


【解决方案1】:

直接回答我建议你不要:

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

改为:

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

进一步改进

  • 使用 FactoryGirl 创建测试对象。
  • 干燥代码

我的版本(假设安装了 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

【讨论】:

  • 谢谢,这正是我想要的。我添加了 FactoryGirl 并使用了您的测试。一切都过去了!
【解决方案2】:

如果您不想安装和设置 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

【讨论】:

    【解决方案3】:

    我认为此测试不需要 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/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-24
      • 1970-01-01
      • 2018-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多