【发布时间】:2011-04-09 13:07:24
【问题描述】:
【问题讨论】:
标签: clojure mocking mockito easymock
【问题讨论】:
标签: clojure mocking mockito easymock
您应该能够使用任何模拟库来模拟协议。在幕后,每个协议都使用 Java 接口作为实现细节,您可以模拟该接口。
也就是说,不要这样做!由于反射、保护级别、最终类等,Java 中的 Mocking 非常复杂。任何时候你想要一个实现协议的 Clojure 对象,只需调用 reify,例如
(defprotocol Foo (method-a [_]) (method-b [_]))
-> Foo
(let [stub (reify Foo (method-a [_] :stubbed))]
(method-a stub))
-> :stubbed
请注意,您不需要存根您不打算调用的方法。
【讨论】:
看起来最新版本的 Midje 很好地提供了此功能。
首先,我想指出,这种模拟在将较大的程序拆分为组件时非常有用(例如,通过依赖注入与像 Stuart Sierra 的 component library 这样的库进行组装)。如果我有一些组件将一组副作用函数隔离到一个概念组件中,我当然想要一个测试框架,它可以让我注入一个替代组件,以便我可以:
您可以使用 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,包括如何在提供的单个函数规范中指定确切的次数,以及如何指示该函数永远不会被调用。
【讨论】: