【问题标题】:Fast (Rspec) tests with and without Rails使用和不使用 Rails 的快速 (Rspec) 测试
【发布时间】:2012-10-13 09:20:29
【问题描述】:

我有两个班级:

1.Sale 是 ActiveRecord 的子类;它的工作是将销售数据持久化到数据库中。

class Sale < ActiveRecord::Base
  def self.total_for_duration(start_date, end_date)
    self.count(conditions: {date: start_date..end_date})
  end
  #...
end

2.SalesReport 是一个标准的 Ruby 类;它的工作是生成和绘制有关销售的信息。

class SalesReport
  def initialize(start_date, end_date)
    @start_date = start_date
    @end_date = end_date
  end

  def sales_in_duration
    Sale.total_for_duration(@start_date, @end_date)
  end
  #...
end

因为我想使用 TDD 并且我希望我的测试能够真正快速地运行,所以我为 SalesReport 编写了一个不会加载 Rails 的规范:

require_relative "../../app/models/sales_report.rb"

class Sale; end
# NOTE I have had to re-define Sale because I don't want to
# require `sale.rb` because it would then require ActiveRecord.

describe SalesReport do
  describe "sales_in_duration" do
    it "calls Sale.total_for_duration" do
      Sale.should_receive(:total_for_duration)
      SalesReport.new.sales_in_duration
    end
  end
end

当我运行 bundle exec rspec spec/models/report_spec.rb 时,此测试有效

但是,当我运行 bundle exec rake spec 并出现错误 superclass mismatch for class Sale (TypeError) 时,此测试失败。我知道错误正在发生,因为 Tap 由 sale.rb 定义并内联在规范中。

所以我的问题是,如果未定义该类,是否有办法存根(或 Mock 或 Double)该类?这将允许我删除内联 class Sale; end,感觉就像一个黑客。

如果不是,我该如何设置我的测试,以便无论我运行 bundle exec rspec 还是 bundle exec rake spec,它们都能正常运行?

如果不是,我编写快速测试的方法是否错误?!

最后,我不想使用 Spork。谢谢!

【问题讨论】:

    标签: ruby rspec tdd stub load-path


    【解决方案1】:

    RSpec 最近添加的stub_const 专为以下情况而设计:

    describe SalesReport do
      before { stub_const("Sale", Class.new) }
    
      describe "sales_in_duration" do
        it "calls Sale.total_for_duration" do
          Sale.should_receive(:total_for_duration)
          SalesReport.new.sales_in_duration
        end
      end
    end
    

    您可能还想使用rspec-fire 来使用测试替身代替Sale,当使用真实Sale 运行测试时,它会自动检查真实Sale 类中存在的所有模拟/存根方法加载类(例如,当您运行测试套件时):

    require 'rspec/fire'
    
    describe SalesReport do
      include RSpec::Fire
    
      describe "sales_in_duration" do
        it "calls Sale.total_for_duration" do
          fire_replaced_class_double("Sale")
          Sale.should_receive(:total_for_duration)
          SalesReport.new.sales_in_duration
        end
      end
    end
    

    如果你在真实的Sale 类上重命名total_for_duration,当你模拟该方法时,rspec-fire 会给你一个错误,因为它在真实的类上不存在。

    【讨论】:

      【解决方案2】:

      一个简单的方法是检查是否已经定义了“销售”

      unless defined?(Sale)
        class Sale; end
      end
      

      在您的测试中,销售也不必是一个类,所以:

      unless defined?(Sale)
        Sale = double('Sale')
      end
      

      【讨论】:

        猜你喜欢
        • 2012-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多