解决部分嘲笑的需要

时间:2013-08-14 14:42:30

标签: ruby oop architecture minitest

我不时会遇到我想在测试中使用类方法的部分模拟的情况。目前,我正在使用 minitest ,它不支持这个(可能因为它首先不是一个好主意......)。

一个例子:

class ImportRunner

  def self.run *ids
    ids.each { |id| ItemImporter.new(id).import }
  end
end

class ItemImporter

  def initialize id
    @id = id
  end

  def import
    do_this
    do_that
  end

  private

    def do_this
      # do something with fetched_data
    end

    def do_that
      # do something with fetched_data
    end

    def fetched_data
      @fetched_data ||= DataFetcher.get @id
    end

end

我想单独测试ImportRunner.run方法(主要是因为ItemImporter#import缓慢/昂贵)。在 rspec 中,我会编写一个这样的测试:

it 'should do an import for each id' do
  first_importer  = mock
  second_importer = mock

  ItemImporter.should_receive(:new).with(123).and_return(first_importer)
  first_importer.should_receive(:import).once
  ItemImporter.should_receive(:new).with(456).and_return(second_importer)
  second_importer.should_receive(:import).once

  ImportRunner.run 123, 456
end

问题的第一部分:是否可以在 minitest 中执行类似操作?


问题的第二部分:是对象协作的形式

collaborator = SomeCollaborator.new a_param
collaborator.do_work
设计不好?如果是这样,你会如何改变它?

2 个答案:

答案 0 :(得分:1)

你所要求的是直接Minitest 几乎。 Minitest :: Mock不支持部分模拟,所以我们尝试通过存根ItemImporter的new方法并返回一个调用模拟返回模拟的lambda来做到这一点。 (模拟中的模拟:Mockception)

def test_imports_for_each_id
  # Set up mock objects
  item_importer   = MiniTest::Mock.new
  first_importer  = MiniTest::Mock.new
  second_importer = MiniTest::Mock.new

  # Set up expectations of calls
  item_importer.expect :new, first_importer,  [123]
  item_importer.expect :new, second_importer, [456]
  first_importer.expect  :import, nil
  second_importer.expect :import, nil

  # Run the import
  ItemImporter.stub :new, lambda { |id| item_importer.new id } do
    ImportRunner.run 123, 456
  end

  # Verify expectations were met
  # item_importer.verify
  first_importer.verify
  second_importer.verify
end

除了调用item_importer.verify之外,这将有效。因为该模拟将返回其他模拟,验证满足所有期望的过程将在first_importersecond_importer模拟中调用其他方法,从而导致它们升起。因此,虽然您可以接近,但您无法完全复制您的rspec代码。要做到这一点,你将不得不使用一个支持部分模拟的不同模拟库,如RR

如果该代码看起来很难看,请不要担心,确实如此。但这不是Minitest的错,它是测试中相互冲突的责任的错误。就像你说的,这可能不是一个好主意。我不知道这个测试应该证明什么。它看起来是指定代码的实现,但它并没有真正传达预期的行为。这是一些人称之为“过度嘲弄”的。

  

模拟和存根是开发人员手中的重要且有用的工具,但它很容易被带走。除了提供虚假的安全感之外,过度模拟的测试也会变得脆弱和嘈杂。 - Rails AntiPatterns

我会重新考虑你试图通过这个测试完成的事情。 Minitest通过设计选择丑陋的东西应该看起来丑陋来帮助你。

答案 1 :(得分:0)

您可以使用Mocha gem。我在大多数测试中也使用MiniTest,并使用Mocha来模拟和存根方法。