模拟Clojure协议

5

EasyMockMockito 这样的流行的 Java 模拟框架能否用于模拟使用 defprotocol 定义的 Clojure 协议?如果可以,如何操作?

2个回答

10

您可以使用任何mock库来模拟协议。在底层,每个协议都使用Java接口作为实现细节,您可以直接对该接口进行mock。

尽管如此,请不要这样做!由于反射、保护级别、最终类等原因,Java中的Mocking非常复杂。每当您需要一个实现协议的Clojure对象时,只需调用reify即可,例如:

 (defprotocol Foo (method-a [_]) (method-b [_]))
 -> Foo

 (let [stub (reify Foo (method-a [_] :stubbed))] 
   (method-a stub))
 -> :stubbed

请注意,您不需要为您不打算调用的方法编写存根。


1
那是桩测试。那模拟呢?你会如何用惯用语定义和验证期望? - Daniel Dinnyes
我同意@Dadinn的观点。似乎仅使用存根来支持关于行为的断言和验证其发生是不够严谨的...例如,验证协议.f被调用了3次...那么,我需要创建一个变量,改变它,然后检查值吗?这并不是非常声明性的。肯定有人写过一些东西,使得这个过程需要更少的样板文件。 - Tony K.

3
看起来Midje的更新版本提供了这个功能。

首先,我想指出,当将大型程序拆分成组件(例如,使用类似Stuart Sierra的组件库的依赖注入库进行组装)时,这种模拟非常有用。如果我有一些组件将一组副作用函数隔离到一个概念组件中,我肯定希望有一个测试框架,可以让我注入一个替代组件,以便我可以:

  1. 编写在组件之前使用该组件的代码(自上而下)。
  2. 从组件的真实实现中独立测试使用该组件的函数。

您可以使用Mockito或其他库,但我认为这种解决方案不会特别优雅。

不幸的是,协议和记录生成的类Midje无法像函数那样轻松地进入其中...因此您确实需要稍微修改代码:

(defrecord-openly SideEffectThing [connection]
 ISideEffect
 (persist [this thing] :unfinished)
 (read [this] :unfinished)
)

请查看Midje的生产模式文档,了解如何使此修改不影响您的生产运行时。

通过使用defrecord-openly定义组件,您可以使用Midje的“provided”机制指定组件方法的行为:

(fact "you can test in terms of a record's methods"
  (let [obj (->SideEffectThing :fake-connection)]
   (user-of-side-effect-thing obj) => 0
   (provided
    (read obj) => -1)
  )
 )

当然,你可以避免在此处依赖生产类型(我会提倡这样做),也可以避免在生产代码中频繁使用defrecord-openly。在这种情况下,只需将上述“SideEffectThing”移动到测试代码中。然后应用程序中的组件系统可以插入真实的组件,但是您的测试可以针对此未实现的测试版本编写。
为了完整起见,我将比较等效的Java Mockito代码与上述解决方案。在Java中:
interface ISideEffect { int read(); void write(Object something); }
class SideEffectThing implements ISideEffect { ... }

// in test sources:
public class SomeOtherComponentSpec {
   private ISideEffect obj;
   @Before
   public void setup() { obj = mock(ISideEffect.class); }
   @Test
   public void does_something_useful() {
      when(obj.read()).thenReturn(-1);
      OtherComponent comp = new OtherComponent(obj);

      int actual = comp.doSomethingUseful();

      assertEquals(0, actual);
      verify(obj).read();
   }

这个Java解决方案模拟了组件,指定了该组件所需的行为,不仅检查组件本身是否正常工作,还检查组件对read()调用的依赖方式。Mockito还支持对参数(和捕获)进行模式匹配以分析组件的使用情况(如果需要)。
Midje示例在很大程度上做到了这一点,并以更清晰的形式呈现。如果您在提供的条款中指示函数使用特定参数进行调用,则测试将在未使用时失败。如果您指定该函数被调用超过一次(但实际没有),则也会失败。例如,要指示read将被调用3次并应返回不同的值:
(fact "you can test in terms of a record's methods"
  (let [obj (->SideEffectThing :fake-connection)]
   (user-of-side-effect-thing obj) => 0
   (provided
    (read obj) => -1
    (read obj) => 6
    (read obj) => 99
    )
  )
 )

这表示您期望调用三次read,并且它应返回指定的值序列。有关更多详细信息,请参见 先决条件文档,包括如何在提供的单个函数规范中指定确切的调用次数以及如何指示该函数永远不会被调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接