它是动态类型,而不是静态类型。 然后,鸭子类型输入将执行与静态类型语言中的接口相同的工作。而且,其类可以在运行时进行修改,以便测试框架可以轻松地对现有类进行存根或模拟方法。Ruby是一种这样的语言。rspec是其针对TDD的首要测试框架。
动态打字如何帮助测试
使用动态类型,您可以通过简单地创建一个类,该类具有与您需要模拟的协作对象相同的接口(方法签名),从而创建模拟对象。例如,假设您有一些发送消息的类:
class MessageSender
def send
# Do something with a side effect
end
end
假设我们有一个使用MessageSender实例的MessageSenderUser:
class MessageSenderUser
def initialize(message_sender)
@message_sender = message_sender
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
注意这里依赖注入的使用,这是单元测试的主要内容。我们将回到这一点。
您希望测试MessageSenderUser#do_stuff
呼叫是否发送两次。就像使用静态类型的语言一样,您可以创建一个模拟MessageSender来计算send
调用次数。但是与静态类型的语言不同,您不需要接口类。您只需继续创建它:
class MockMessageSender
attr_accessor :send_count
def initialize
@send_count = 0
end
def send
@send_count += 1
end
end
并在测试中使用它:
mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)
就其本身而言,与静态类型的语言相比,动态类型的语言的“鸭子类型”在测试中并没有增加多少。但是,如果没有关闭类但可以在运行时进行修改,该怎么办?那是改变游戏规则的人。让我们看看如何。
如果您不必使用依赖注入使类可测试怎么办?
假设MessageSenderUser仅将使用MessageSender发送消息,并且您无需允许将MessageSender替换为其他类。在单个程序中,通常是这种情况。让我们重写MessageSenderUser,以便它简单地创建和使用MessageSender,而无需依赖项注入。
class MessageSenderUser
def initialize
@message_sender = MessageSender.new
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
现在,MessageSenderUser更加易于使用:创建它的任何人都无需创建MessageSender即可使用。在这个简单的示例中,这看起来并没有太大的改进,但是现在想象一下,MessageSenderUser是在多个位置创建的,或者它具有三个依赖关系。现在,该系统具有大量传递实例,只是为了使单元测试满意,而不是因为它根本无法改善设计。
开放类使您无需依赖注入就可以进行测试
具有动态类型和开放类的语言的测试框架可以使TDD变得相当不错。这是针对MessageSenderUser的rspec测试的代码片段:
mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff
这就是整个测试。如果MessageSenderUser#do_stuff
没有MessageSender#send
准确地调用两次,则此测试将失败。真正的MessageSender类永远不会被调用:我们告诉测试,每当有人尝试创建MessageSender时,他们应该获取我们的模拟MessageSender。无需依赖注入。
在如此简单的测试中做很多事情真是太好了。除非确实对您的设计有意义,否则不必使用依赖注入会更好。
但这与开放类有什么关系?请注意对的调用MessageSender.should_receive
。编写MessageSender时,我们没有定义#should_receive,所以谁做的呢?答案是测试框架通过对系统类进行一些仔细的修改,能够使它看起来像#should_receive通过在每个对象上定义的一样。如果您认为修改这样的系统类需要谨慎,那是对的。但这对于测试库在这里所做的事情来说是完美的,开放类使之成为可能。