【问题标题】:Simulating race conditions in RSpec unit tests在 RSpec 单元测试中模拟竞争条件
【发布时间】:2011-01-02 07:41:42
【问题描述】:

我们有一个异步任务,它为一个对象执行可能需要长时间运行的计算。然后将结果缓存在对象上。为了防止多个任务重复相同的工作,我们通过原子 SQL 更新添加了锁定:

UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0

锁定仅适用于异步任务。对象本身仍可能由用户更新。如果发生这种情况,旧版本对象的任何未完成任务都应丢弃其结果,因为它们可能已过时。使用原子 SQL 更新也很容易做到这一点:

UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1

如果对象已更新,则其版本将不匹配,因此将丢弃结果。

这两个原子更新应该处理任何可能的竞争条件。问题是如何在单元测试中验证这一点。

第一个信号量很容易测试,因为它只是设置两个不同的测试,有两种可能的情况:(1)对象被锁定的地方和(2)对象没有被锁定的地方。 (我们不需要测试 SQL 查询的原子性,这应该是数据库供应商的责任。)

如何测试第二个信号量?在第一个信号量之后但在第二个信号量之前的某个时间,第三方需要更改对象。这将需要暂停执行,以便可以可靠且一致地执行更新,但我知道不支持使用 RSpec 注入断点。有没有办法做到这一点?或者是否有其他一些我忽略的技术来模拟这种竞争条件?

【问题讨论】:

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


    【解决方案1】:

    您可以从电子制造中借鉴一个想法,并将测试挂钩直接放入生产代码中。就像电路板可以制造有专门的地方供测试设备控制和探测电路一样,我们也可以用代码做同样的事情。

    假设我们有一些代码在数据库中插入一行:

    class TestSubject
    
      def insert_unless_exists
        if !row_exists?
          insert_row
        end
      end
    
    end
    

    但此代码在多台计算机上运行。那么就有一个竞争条件,因为另一个进程可能会在我们的测试和我们的插入之间插入行,从而导致 DuplicateKey 异常。我们想测试我们的代码是否处理了由该竞争条件导致的异常。为此,我们的测试需要在调用row_exists? 之后、调用insert_row 之前插入行。所以让我们在那里添加一个测试钩子:

    class TestSubject
    
      def insert_unless_exists
        if !row_exists?
          before_insert_row_hook
          insert_row
        end
      end
    
      def before_insert_row_hook
      end
    
    end
    

    当在野外运行时,钩子除了消耗一点点 CPU 时间外什么都不做。但是当代码被测试竞争条件时,测试猴子补丁 before_insert_row_hook:

    class TestSubject
      def before_insert_row_hook
        insert_row
      end
    end
    

    这不是很狡猾吗?就像寄生蜂幼虫劫持了毫无防备的毛毛虫的身体一样,测试劫持了被测代码,以便它创建我们需要测试的确切条件。

    这个想法就像 XOR 游标一样简单,所以我怀疑很多程序员已经独立发明了它。我发现它对于测试具有竞争条件的代码通常很有用。希望对你有帮助。

    【讨论】:

    • 啊哈。这样就可以了。虽然不是添加显式钩子,我可能只是使用alias_method_chain 来扩展在两个信号量之间必须调用的方法的功能——长时间运行的任务。
    • 顺便说一句,很想看看你是如何用链接编码的。有趣的想法。
    • 非常聪明的解决方案。我喜欢它。
    • @WayneConrad 我不确定你是邪恶天才还是园艺天才,但这是……天才。谢谢和被盗!
    • @JoeCairns 我很高兴成为天才。感谢您的夸奖,我很高兴这个想法对您有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-21
    • 2017-02-11
    • 2018-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多