FlexMock是Jim 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
相关链接
- 项目主页
- 文档
- Bug/Issue
- 持续集成
编译 SegmentFault