【问题标题】:Mocking Clojure protocols模拟 Clojure 协议
【发布时间】:2011-04-09 13:07:24
【问题描述】:

可以使用EasyMockMockito 等流行的Java 模拟框架之一来模拟用defprotocol 定义的Clojure 协议吗?如果有,怎么做?

【问题讨论】:

    标签: clojure mocking mockito easymock


    【解决方案1】:

    您应该能够使用任何模拟库来模拟协议。在幕后,每个协议都使用 Java 接口作为实现细节,您可以模拟该接口。

    也就是说,不要这样做!由于反射、保护级别、最终类等,Java 中的 Mocking 非常复杂。任何时候你想要一个实现协议的 Clojure 对象,只需调用 reify,例如

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

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

    【讨论】:

    • 那是存根。嘲讽呢?您将如何惯用地定义和验证期望?
    • 我同意@Dadinn。似乎对陈述有关行为的断言并验证它们是否发生具有一流的支持只会因为存根而变得草率......例如验证 protocol.f 被调用了 3 次......什么,我创建一个 var,对其进行变异,然后检查值?这不是很明确。当然有人写了一些东西来减少样板文件。
    【解决方案2】:

    看起来最新版本的 Midje 很好地提供了此功能。

    首先,我想指出,这种模拟在将较大的程序拆分为组件时非常有用(例如,通过依赖注入与像 Stuart Sierra 的 component library 这样的库进行组装)。如果我有一些组件将一组副作用函数隔离到一个概念组件中,我当然想要一个测试框架,它可以让我注入一个替代组件,以便我可以:

    1. 编写在组件存在之前就使用它的代码(自上而下)。
    2. 测试使用该组件的函数与组件的实际实现隔离。

    您可以使用 Mockito 或其他一些库,但我同意这样的解决方案不会特别优雅。

    不幸的是,协议和记录生成的类是 Midje 无法像函数一样容易破解的......所以你必须稍微修改你的代码:

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

    请参阅Midje's documentation on production mode,了解有关如何使此修改不影响您的生产运行时的详细信息。

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

    (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。在这种情况下,只需将 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 示例做了很多工作,而且形式更加清晰。如果您指示(在提供的子句中)使用特定参数调用该函数,则如果不是,则测试将失败。如果您指定该函数被多次调用(实际上不是),那么这是一个失败。例如,表示读取将被调用 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 被调用 3 次,并且它应该返回指定的值序列。有关更多详细信息,请参阅docs on prerequisites,包括如何在提供的单个函数规范中指定确切的次数,以及如何指示该函数永远不会被调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-18
      • 2012-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      相关资源
      最近更新 更多