如何在Rails模型的Rspec测试中禁用belongs_to:touch选项?

时间:2013-09-20 17:47:55

标签: ruby-on-rails rspec associations metaprogramming belongs-to

拥有大型模型堆栈并广泛使用玩偶缓存技术,最终会在模型更新后“触摸”许多父模型。

在测试时,除非您尝试专门测试该功能,否则这似乎是浪费时间。

有没有办法阻止模型touch他们的belongs_to关联用于测试环境或测试级别?

更新1:

我对案件的第一次尝试将是

# /config/initializers/extensions.rb
#
class ActiveRecord::Base
  def self.without_touch_for_association(association_name, &block)
    association_name = association_name.to_sym
    association = self.reflect_on_all_associations(:belongs_to).select { |reflection| reflection.name == association_name }.first
    options = association.options
    association.instance_variable_set :@options, options.except(:touch)

    yield

    association.instance_variable_set :@options, options
  end
end

Post.without_touch_for_association(:user) do
  Post.last.save
end

当然,没有成功,保存Post.last仍然触及User

更新理由:

我理解并同意这种方法可能是错误的根源,而且根本不是一个好的做法。问题是我有一个巨大的套件,有很多集成和单元测试。玩偶缓存也深入到模型树中。每当我查看日志时,我都会看到大量与触摸相关的查询。我知道最好的方法是优化单元测试以增加更多的模拟和存根以及更少的持久性。在集成测试中解决问题更加困难。

无论如何,我是为了学习和研究而提出这个问题。我有兴趣探索这种技术的潜在速度改进。

解决方案:请参阅下面我自己的答案,了解工作代码。

4 个答案:

答案 0 :(得分:10)

假设您使用的是Rails 4.1.4或更高版本:

User.no_touching do
  Post.last.save
end

甚至

ActiveRecord::Base.no_touching do
  Post.last.save
end

请参阅ActiveRecord::NoTouching

答案 1 :(得分:3)

我不同意为测试目的而更改代码的概念。从我的观点来看,测试应该是一个独立的程序。

正如我所看到的那样,您应该为测试套件提供一种方法,仅在某些情况下改变模型的行为。

以下代码

class Book < ActiveRecord::Base
  belongs_to :author, touch: true
end

class Author < ActiveRecord::Base
  has_many :books
end

您的情况将定义实例方法

belongs_to_touch_after_save_or_destroy_for_author

Book课程的幕后。 (感谢AR http://apidock.com/rails/ActiveRecord/Associations/Builder/BelongsTo/add_touch_callbacks

因此,在您的测试代码中,您可以覆盖该方法以执行不同的操作或根本不执行任何操作!

在我的情况下,我使用Rspec和FactoryGirl,所以我做的是为Book类创建一个特殊的工厂特性,为该对象重新定义belongs_to_touch_after_save_or_destroy_for_author

FactoryGirl.define do
  factory :book do
    ...
    ...
  end

  trait :no_touch do
    before(:create) do |book_no_touch|
      def book_no_touch.belongs_to_touch_after_save_or_destroy_for_author
        true
      end
    end
  end
end

这样,当你需要测试触摸相关对象无关紧要的东西时,可以用该工厂创建一个书对象

book = FactoryGirl.create(:book, :no_touch)

答案 2 :(得分:3)

对于Rails&gt; = 4.2

感谢@Dorian,在Rails 4.2中,可以使用ActiveRecord::NoTouching

对于Rails&lt; 4.2

我在rspec支持文件中的工作代码:

# /spec/support/active_record_extensions.rb
class ActiveRecord::Base
  def self.without_touch_for_association(association, &block)
    method_name = :"belongs_to_touch_after_save_or_destroy_for_#{association}"
    return unless self.instance_methods.include?(method_name)

    method = self.send(:instance_method, method_name)
    self.send(:define_method, method_name) { true }

    yield

    self.send(:define_method, method_name, method)
    nil
  end

  def self.disable_touch_associations!
    associations = self.reflect_on_all_associations(:belongs_to)
    associations.each do |association|
      self.without_touch_for_association association.name do
        return
      end
    end
    nil
  end
end

将此添加到./spec/spec_helper.rb以禁用针对整个测试套件定义的任何模型的所有触摸调用:

RSpec.configure do |config|
  if ENV['SILENCE_TOUCHES']
    config.before :suite do
      ActiveRecord::Base.descendants.each {|model| model.disable_touch_associations! }
    end
  end
end

在特定测试中暂时禁用模型和关联的触摸。

Post.without_touch_for_association(:user) do
  Post.last.save
end

感谢下面的@xlembouras指出我正确的方向!

我在测试中使用此功能,我注意到测试套件速度降低了25%,对于30分钟的测试套件。经过更深入的研究后,我可能会发布更准确的结果。

答案 3 :(得分:0)

我不确定这是否可行,但您可以尝试以下方法:

belongs_to :foo, touch: APP_CONFIG['doll_touch']

其中APP_CONFIG是在此guide之后设置的应用程序参数。

因此,在配置的生产/开发部分,您将doll_touch设置为true,将测试设置为false