4

我正在将我最喜欢的 Java/JavaScript Mocktito 库移植到 Smalltalk。我目前正处于实施 Spy 以对真实对象进行存根的阶段。当一个被监视的对象调用它自己的被存根的方法时,我的问题就出现了。代替:

self aMethod.

我宁愿将调用委托给 spy 对象:

spyObject aMethod.

这是预期行为的场景测试:

realObject := RealObjectForTesting new.
spyedObject := Spy new: realObject.
spyedObject when: #accesorWhichReturnsValue thenReturn: 'stubbed value'.

spyedObject accesorWhichCallsSelf.

self assert: (spyedObject verify: #accesorWhichReturnsValue).

有什么建议吗?

4

3 回答 3

3

您可以使用ObjectsAsMethodsWrapper 库直接包装RealObjectUnderTests 。这提供了一个方便的 API 来安装和删除包装器,以及一些方便的预定义包装器。CompiledMethod

这些将拦截自发送,因为包装器安装在真实对象的方法字典中,因此可以在将消息传递给底层之前对消息执行任意更改CompiledMethod

虽然我的示例展示了如何在不接触源代码的情况下记忆方法调用,但它应该为您提供模拟方法调用所需的基本知识。

这种特殊的技术确实有一个限制:它会拦截自我发送到类本身定义的消息。因此,如果Foo子类Bar并且您在 上安装包装器Foo,您将不会拦截构成Bar' 协议一部分的消息(当然,除非您也包装了这些消息)。

将无法ifTrue:ifFalse:在 Squeak 或 Pharo 图像中截获timesRepeat或类似的消息(也可能在 GNU Smalltalk 中),因为这些不是消息发送:编译时转换将这些消息发送到跳转字节码中。(消息发送的错觉相对令人信服,因为Decompiler知道如何将字节码转换回ifTrue:ifFalse:或其他任何内容。)

于 2012-11-01T19:17:58.983 回答
3

您使您的“间谍”成为仅实现的“包装器”对象,doesNotUnderstand:然后使用become:.

将为所有消息调用spy 的doesNotUnderstand:方法,然后您可以记录其参数(这是一个 Message 对象)并将其发送到原始对象。

如果您浏览doesNotUnderstand:Smalltalk 图像中的实现者,您可能会发现几个示例(例如,在 Squeak 中有MessageCatcherObjectViewer)。

于 2012-10-31T15:38:08.593 回答
2

Smalltalk 没有用于拦截发送给自己的内置机制,因此您将不得不求助于奇异的措施。

可能最简单的方法是从原始对象中动态窃取方法。正如伯特建议的那样,您的间谍将使用#become: 替换目标。然后,当间谍接收到消息时,您不会将消息转发给原始对象,而是在其类中查找选择器并以间谍作为接收者来执行它。针对任意接收者执行编译方法的机制因方言而异。在 Squeak 中,它是 CompiledMethod 类>>receiver:withArguments:executeMethod:。

正如我所说,这是非常奇特的——您需要在创建 spy 时为其生成一个自定义类,并确保它与目标对象具有相同的结构,以便编译的方法可以与新的接收器正确工作。您还必须将目标的状态复制到间谍中,并在完成间谍后将其复制回原始对象。最后,您必须找到一个存放目标的地方,因为间谍的状态必须与目标的状态相匹配。这一切都是可行的,但并不简单。您将动态生成类和方法,这需要对系统有低层次的了解。

于 2012-10-31T17:31:19.877 回答