FlexMockJim Weirich开发的Ruby单元测试mock库。

编写测试的时候,可能需要和系统内的某个模块或系统外某个实体交互,例如数据库读写、邮件发送等。这时就需要使用 mock 技术来模拟。

安装

gem install flexmock

简单的例子

我们有一个获取数据的类TemperatureSampler,从温度感应器中读取数据,并返回三次读取的平均值。当我们运行测试的时候,我们并没有真正的温度感性器,所以我们需要一个mock对象来响应read_temperature消息:

require 'test/unit'
require 'flexmock/test_unit'

class TemperatureSampler
  def initialize(sensor)
    @sensor = sensor
  end

  def average_temp
    total = (0...3).collect {
      @sensor.read_temperature
    }.inject { |i, s| i + s }
    total / 3.0
  end
end

class TestTemperatureSampler < Test::Unit::TestCase
  def test_sensor_can_average_three_temperature_readings
    sensor = flexmock("temp")
    sensor.should_receive(:read_temperature).times(3).
      and_return(10, 12, 14)

    sampler = TemperatureSampler.new(sensor)
    assert_equal 12, sampler.average_temp
  end
end

Test::Unit集成

FlexMock和Test::Unit配合良好,只需在测试文件的头部require一下flexmock/test_unit。然后使用flexmock方法来创建mock,创建的mock会在每个测试的结尾自动验证,然后结束。

例如:

require 'flexmock/test_unit'

class TestDog < Test::Unit::TestCase
  def test_dog_wags
    tail_mock = flexmock(:wag => :happy)
    assert_equal :happy, tail_mock.wag
  end
end

RSpec集成

RSpec集成也很简单:

RSpec.configure do |config|
    config.mock_with :flexmock
  end

  describe "Using FlexMock with RSpec" do
    it "should be able to create a mock" do
      m = flexmock(:foo => :bar)
      m.foo.should === :bar
    end
  end

创建局部mock

有时候我们希望mock对象中的一两个方法的行为,但是不改变该对象的其他部分。例如,假设我们有一个Dog对象,这个对象使用Woofer对象来咆哮:

class Dog
  def initialize
    @woofer = Woofer.new
  end
  def bark
    @woofer.woof
  end
  def wag
    :happy
  end
end

现在我们想要测试Dog,但是不想使用真正的Woofer对象(因为Woofer会播放一段狗叫的音频,在测试时听到狗叫有点烦人)。

因此我们使用FlexMock来替换bark方法,这样我们就可以创建一个使用mock过的Woofer对象的Dog对象:

class TestDogBarking < Test::Unit::TestCase
  include FlexMock::TestCase

  # Setup the tests by mocking the +new+ method of
  # Woofer and return a mock woofer.
  def setup
    @dog = Dog.new
    flexmock(@dog, :bark => :grrr)
  end

  def test_dog
    assert_equal :grrr, @dog.bark   # Mocked Method
    assert_equal :happy, @dog.wag    # Normal Method
  end
end

当测试结束后,被mock的方法会恢复它原来的状态。

这一技术的灵感来自于Mocha项目的Stuba库。

Mock类、对象

当然,我们也可以直接mock Woofer类返回mock:

class TestDogBarking < Test::Unit::TestCase
  include FlexMock::TestCase

  # Setup the tests by mocking the `new` method of
  # Woofer and return a mock woofer.
  def setup
    flexmock(Woofer).should_receive(:new).
       and_return(flexmock(:woof => :grrr))
    @dog = Dog.new
  end

  def test_dog
    assert_equal :grrrr, @dog.bark  # Calls woof on mock object
                                    # returned by Woofer.new
  end
end

Mock某个类创建的所有实例的行为

有时候mock单个对象是不够的,我们希望mock某个类创建的所有实例。用FlexMock很容易做到这点:

class TestDogBarking < Test::Unit::TestCase
  include FlexMock::TestCase

  # Setup the tests by mocking Woofer to always
  # return partial mocks.
  def setup
    flexmock(Woofer).new_instances.should_receive(:woof => :grrr)
  end

  def test_dog
    assert_equal :grrrr, Dog.new.bark  # All dog objects
    assert_equal :grrrr, Dog.new.bark  # are mocked.
  end
end

相关链接

Jim Weirich


编译 SegmentFault


weakish
24.6k 声望844 粉丝

a vigorously lazy deadbeat with matured immaturity