2011-04-09 97 views

回答

9

你应该可以使用任何模拟库来模拟协议。在封面之下,每个协议都使用Java接口作为实现细节,您可以嘲笑该接口。

这就是说,不要这样做!由于反射,保护级别,最终类别等原因,Java中的嘲讽非常复杂。任何时候如果您想要实现协议的Clojure对象,只需拨打reify(例如,

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

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

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

+1

这就是存根。怎么样嘲笑?你如何定义和验证期望_idiomatically_? – 2013-03-18 16:53:35

+0

我同意@Dadinn。看起来有一流的支持来陈述关于行为的断言,并证实他们发生的事情会随着st ...而变得渺茫......例如,验证protocol.f被调用了3次...什么,我创建一个var,mutate它,然后检查值?这不是很说明性的。当然有人写了一些东西,使这需要少一点的样板。 – 2015-06-03 21:14:31

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不会影响您的生产运行时。

(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定义您的组件,您可以使用Midje的“规定”机制获得指定的组件的方法的行为的能力(我会主张),并且还要避免在整个生产代码中公开使用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 
    ) 
) 
) 

表明您希望阅读被称为三次,它应该返回值的指定序列。有关更多详细信息,请参阅docs on prerequisites,其中包括如何在提供的单个函数规范中指定确切的次数,以及如何指示该函数从不被调用。