【问题标题】:Rspec tests failing randomly when analysing ActiveRecord objects generated by Mongoid events分析 Mongoid 事件生成的 ActiveRecord 对象时,Rspec 测试随机失败
【发布时间】:2014-03-29 01:50:25
【问题描述】:

我实现了一个基于 Mongoid 的活动日志机制,将事件保存在 MongoDB 中。

Mongoid 模型 Activity 具有 after_create 事件,这些事件根据记录的活动类型执行不同的任务:(简化示例)

class Activity
  include Mongoid::Document

  after_create do |activity|
    method_name = "after_#{activity.event_type}"
    send(method_name) if respond_to? method_name
  end

  def after_user_did_something
    MyItem.create!(:type => :user_did_something)
  end
end

测试看起来像这样:

 it 'should hide previous [objects] create a new updated one' do
      2.times do 
        user.log_activity(:user_did_something) 
      end
      items = MyItems.where(:type => :user_did_something)
      items.count.should == 2
    end
 end

有时,测试在 items.count 为 0 而不是 2 时失败。 仅当从命令行运行时才会发生这种情况rspec spec 仅运行此测试或使用 Guard 运行所有测试时,它永远不会发生。

【问题讨论】:

  • 你能提供log_activity代码吗?

标签: ruby-on-rails mongodb activerecord rspec


【解决方案1】:

您的测试涵盖了单元测试的很多内容:

  1. 调用了after_create 回调
  2. after_user_did_something 被调用
  3. MyItem 对象已创建。

我建议你把它分解成几个单元测试,每个测试一个的东西。您从中获得的附加价值是,至少,您会知道测试的哪个部分实际上失败了......

class Activity
  include Mongoid::Document

  after_create { |activity| my_after_create_callback(activity.event_type) }

  def my_after_create_callback(activity_type)
    method_name = "after_#{activity_type}"
    send(method_name) if respond_to? method_name
  end

  def after_user_did_something
    MyItem.create!(:type => :user_did_something)
  end
end

it 'should call after_create' do
  expect_any_instance_of(Activity).to receive(:my_after_create_callback)
                                  .with(:user_did_something)

  user.log_activity(:type => :user_did_something)
end

it 'should call the correct after activity method' do
  expect_any_instance_of(Activity).to receive :after_user_did_something

  user.log_activity(:type => :user_did_something)
end

it 'should create new MyItem' do
  expect(MyItem).to receive(:create!).with(:type => :user_did_something)

  Activity.new.after_user_did_something
end

【讨论】:

    【解决方案2】:

    在典型的 Mongodb 设置中,数据库写入成功返回与可以读取数据之间可能存在延迟。这有两个原因:

    • 为了提高性能,“不安全”写入可以在数据提交到磁盘之前返回。
    • Mongodb 使用副本集,存在复制延迟。通常读取是作为负载平衡的一种形式分发到副本的,因此即使您使用安全写入,您也可能从与您刚刚写入的服务器不同的服务器读取,因此看不到您刚刚写入的数据。李>

    为确保您始终可以立即回读刚刚使用 Mongoid 写入的数据,您需要设置数据库会话选项consistency: :strong, safe: true,这两个选项都不是默认值。

    【讨论】:

    • 当您说“设置数据库会话选项”时,我们如何在config/mongoid.yml Rails 应用程序中做到这一点?这些键需要在config/mongoid.yml文件的哪个父键中设置?
    【解决方案3】:

    假设问题出在您的测试设置(而不是您的代码)中的某些竞争条件,我建议使用 rspec 的expectations,它应该等待在数据库中创建对象后再计算它们:

     it 'should hide previous [objects] create a new updated one' do
      items = MyItems.where(:type => :user_did_something)
    
      expect { 2.times { user.log_activity(:user_did_something) } }.
      to change { items.count }.from(0).to(2)
    end
    

    [编辑] 为了使测试整体更清晰(这不会影响行为,我不认为),您还可以使用 rspec 的延迟加载 let,如下所示:

    let(:items_count) { MyItems.where(:type => :user_did_something).count }
    
    it 'should hide previous [objects] create a new updated one' do
      expect { 2.times { user.log_activity(:user_did_something) } }.
      to change { items_count }.from(0).to(2)
    end
    

    【讨论】:

      猜你喜欢
      • 2016-01-31
      • 2015-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-13
      • 2021-06-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多