【问题标题】:Unit testing code that references Rails models without loading the models引用 Rails 模型而不加载模型的单元测试代码
【发布时间】:2019-01-25 22:24:21
【问题描述】:

我正在尝试对具有调用 Rails 模型上的类方法的方法的普通旧 Ruby 对象进行单元测试。 Rails 应用程序非常大(加载需要 10 秒),所以我宁愿避免加载所有 Rails 来进行应该在 1 秒内运行的单元测试。

例子:

class Foo
  def bar
    SomeRailsModel.quxo(3)
  end
end

RSpec.describe Foo do
  let(:instance) { Foo.new }

  it 'calls quxo on SomeRailsModel' do
    expect(SomeRailsModel).to receive(:quxo)
    instance.bar
  end
end

这里的问题是我需要require 'rails_helper' 来加载Rails 以便app/models/some_rails_model 可用。由于 Rails 的依赖,这会导致单元测试缓慢。

我尝试在本地定义常量,然后使用常规 spec_helper 哪种方法有效。

例子:

RSpec.describe Foo do
  let(:instance) { Foo.new }
  SomeRailsModel = Object.new unless Kernel.const_defined?(:SomeRailsModel)

  it 'calls quxo on SomeRailsModel' do
    expect(SomeRailsModel).to receive(:quxo)
    instance.bar
  end
end

这段代码让我避免加载所有 Rails 并且执行得非常快。不幸的是,默认情况下(我喜欢这个)RSpec 将常量视为部分双精度并抱怨我的SomeRailsModel 常量不响应quxo 消息。验证双打很好,我想保留那个安全带。我可以通过将验证包装在 RSpec 定义的特殊块中来单独禁用验证。

最后,问题。在不需要所有 Rails 的情况下,在 PORO 上进行快速单元测试的推荐方法是什么?同时保持启用验证双打功能?有没有办法创建一个“苗条”rails_helper,它可以只加载app/modelsActiveRecord 的最小子集以使验证工作?

【问题讨论】:

    标签: ruby-on-rails ruby unit-testing rspec


    【解决方案1】:

    在与同事讨论了一些想法后,这是共识解决方案:

    class Foo
      def bar
        SomeRailsModel.quxo(3)
      end
    end
    
    require 'spec_helper' # all we need!
    
    RSpec.describe Foo do
      let(:instance) { Foo.new }
      let(:stubbed_model) do
        unless Kernel.const_defined?("::SomeRailsModel")
          Class.new { def self.quxo(*); end }
        else
          SomeRailsModel
        end
      end
    
      before { stub_const("SomeRailsModel", stubbed_model) }
    
      it 'calls quxo on SomeRailsModel' do
        expect(stubbed_model).to receive(:quxo)
        instance.bar
      end
    end
    

    在本地运行时,我们将检查模型类是否已经定义。如果有,使用它,因为我们已经为加载该文件付出了代价。如果不是,则创建一个实现被测接口的匿名类。使用stub_const 在匿名类或真实交易中存根。

    对于本地测试,这将非常快。对于在 CI 服务器上运行的测试,我们将检测到模型已经加载并优先使用它。在所有情况下,我们都会自动进行双重方法验证。

    如果真正的 Rails 模型接口发生变化但匿名类落后,CI 运行将捕获它(或集成测试将捕获它)。

    更新: 我们可能会使用 spec_helper.rb 中的辅助方法来稍微干燥一下。如:

    def model_const_stub(name, &blk)
      klass = unless Kernel.const_defined?('::' + name.to_s)
                Class.new(&blk)
              else
                Kernel.const_get(name.to_s)
              end
    
      stub_const(name.to_s, klass)
      klass
    end
    
    # DRYer!
    let(:model) do
      model_const_stub('SomeRailsModel') do
        def self.quxo(*); end
      end
    end
    

    可能不是最终版本,但这给了我们方向的味道。

    【讨论】:

      猜你喜欢
      • 2019-06-21
      • 2015-06-24
      • 2017-12-19
      • 2010-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多