在Java世界中,当涉及到开发单元测试时,我遵循的是“对接口进行测试”的方法。这意味着,如果我有一个Java接口,我将为该接口编写一个单元测试类(从JUnit的TestCase或其他任何东西扩展而来);以测试该接口。这个类将是抽象的,并且将包含一系列用于测试接口方法的测试方法。下面是一个简单的例子:
/** my interface */
public interface MyFooInterface {
int foo();
String bar();
}
/** some implementation */
public class MyFooImplA implements MyFooInterface {
public int foo() { ... }
public String bar() { ... }
}
/** some other implementation */
public class MyFooImplB implements MyFooInterface {
public int foo() { ... }
public String bar() { ... }
}
/** my test case for my interface */
public abstract TestMyFooInterface extends TestCase {
private MyFooInterface objUnderTest;
public abstract MyFooInterface getMyFooInterface();
public void setUp() {
objUnderTest = getMyFooInterface();
}
public void testFoo() {
... bunch of assertions on 'objUnderTest'...
}
public void testBar() {
... bunch of assertions on 'objUnderTest'...
}
}
/** concrete test class, with very little work to do */
public TestMyFooImplA extends TestMyFooInterface {
public MyFooInterface getMyFooInterface() {
return new MyFooImplA();
}
}
/** another concrete test class, with very little work to do */
public TestMyFooImplB extends TestMyFooInterface {
public MyFooInterface getMyFooInterface() {
return new MyFooImplB();
}
}所以在这里我们有一个很棒的东西。无论我们有多少个MyFooInterface实现,我们只需要编写一组单元测试(用TestMyFooInterface.java)来确保MyFooInterface的契约正确性。这些具体的测试用例很无聊;它们所需要做的就是提供一个“getMyFooInterface”的实现;它们只是通过构造正确的实现类来做到这一点。现在,当我运行这些测试时,将为每个具体测试类调用TestMyFooInterface中的每个测试方法。顺便说一下,当我说“当我运行这些测试时”,这意味着将创建一个TestMyFooImplA的实例(因为它是测试工具发现的一个具体的测试用例;基于Ant或Maven的东西),并且它的所有“测试”方法都将被运行(即,来自TestMyFooInterface的所有方法)。哈哈!我们只需要编写一组测试方法,它们将为我们创建的每个具体的测试用例实现运行(这只需要几行代码!)
嗯,当涉及到协议和记录时,我想在Clojure中反映出同样的方法,但我遇到了一些问题。此外,我还想验证这种方法在Clojure领域是否合理。
到目前为止,我在Clojure中的代码如下。这是我的“界面”:
(ns myabstractions)
(defprotocol MyFooProtocol
(foo [this] "returns some int")
(bar [this] "returns some string"))下面是一个实现:
(ns myfoo-a-impl
(:use [myabstractions]))
(defrecord MyFooAImplementation [field-a field-b]
MyFooProtocol
(foo [this] ...impl here...)
(bar [this] ...impl here...))和另一个实现:
(ns myfoo-b-impl
(:use [myabstractions]))
(defrecord MyFooBImplementation [field-1 field-2]
MyFooProtocol
(foo [this] ...impl here...)
(bar [this] ...impl here...))因此,在这一点上,我在某种程度上处于与我熟悉的OO Java世界中相同的位置。我有两个MyFooProtocol协议的实现。每个实现的'foo‘和'bar’函数都应该遵守MyFooProtocol中记录的函数契约。
在我看来,我只想为'foo‘和'bar’创建一组测试,尽管我有多个实现,就像我在Java示例中所做的那样。我创建了我的测试:
(ns myfooprotocol-tests)
(defn testFoo [foo-f myFoo]
(let [fooResult (foo-f myFoo)]
(...some expression that returns a boolean...)))
(defn testBar [bar-f myBar]
(let [barResult (bar-f myBar)]
(...some expression that returns a boolean...)))太好了,我只写了一次测试。上面的每个函数都返回一个布尔值,有效地表示一些测试用例/断言。在现实中,我会有很多很多这样的东西(对于我想要做的每个断言)。现在,我需要创建我的“实现”测试用例。因为Clojure不是面向对象的,所以我不能像上面的Java例子那样做,所以我是这么想的:
(ns myfooATests
(:use [myfooprotocol-tests :only [testFoo testBar]])
(:import [myfoo_a_impl MyFooAImplementation])
(:use [abstractions])
(:require [myfoo-a-impl])
(:use [clojure.test]))
(deftest testForFoo []
(is (testFoo myfoo-a-impl/foo (MyFooAImplementation. 'a 'b))))
(deftest testForBar []
(is (testBar myfoo-a-impl/bar (MyFooAImplementation. 'a 'b))))现在来看另一个测试用例的实现:
(ns myfooBTests
(:use [myfooprotocol-tests :only [testFoo testBar]])
(:import [myfoo_b_impl MyFooAImplementation])
(:use [abstractions])
(:require [myfoo-b-impl])
(:use [clojure.test]))
(deftest testForFoo []
(is (testFoo myfoo-b-impl/foo (MyFooBImplementation. '1 '2))))
(deftest testForBar []
(is (testBar myfoo-b-impl/bar (MyFooBImplementation. '1 '2))))我的两个具体的测试实现(myFooATests和myFooBTests名称空间)看起来很冗长,但它们真正做的只是将断言逻辑委托给myfooprotocol- test名称空间中的'testFoo‘和'testBar’函数。这只是个样板代码。
但是有个小问题。在最后两个清单中,'testFoo‘和'testBar’的第一个参数是'myfoo-#-impl/foo‘或'myfoo-#-impl/bar’(其中'#‘是a或b)。但是这不起作用,因为'foo‘和'bar’函数被埋藏在defprotocol中,我不能以这种方式访问它们。
因为我在很大程度上是孤立地学习Clojure,所以我想联系SO社区并尝试获得一些帮助。首先,我在Clojure代码中所做的事情看起来是否合理?也就是说,这个想法试图“测试一次接口(err,协议)”-这在Clojure领域是一个有价值的目标吗?(我内心的干涩是这样说的;我内心的OO实践者也是如此)。如果我对Clojure中协议和记录之间的关系的解释是正确的(即,一种接口和实现伙伴的形式),那么我真的只想写一次我的测试(就像我在‘myfooprotocol-test’命名空间中尝试做的那样)。
其次,假设所有这些都是合理的,我如何有效地传递在'myfoo-a-impl‘和'myfoo-b-impl’名称空间的defrecords中定义的'foo‘和'bar’函数?获取它们的语法是什么?
谢谢您抽时间见我。
发布于 2013-05-16 12:56:14
首先是简单的部分-是的,你测试协议的各种实现的想法是有意义的,它是有用的。
现在是非常简单的部分,即如何去做。可以将协议看作是在名称空间中创建函数(理论上还没有实现,因为当您扩展该协议时会发生这种情况)。所以当你说:
(ns myabstractions)
(defprotocol MyFooProtocol
(foo [this] "returns some int")
(bar [this] "returns some string"))这意味着myabstractions现在有两个名为foo和bar的函数。因为它们只是函数,所以我们可以很容易地从这个名称空间引用它们,即myabstractions/foo或myabstractions/bar。这清楚地表明,您不需要将这些函数传递给通用的测试名称空间函数,它们只需要一个类型(在您的例子中是一个记录),它们可以在其上调用foo或bar,因此:
(ns myfooprotocol-tests (:use [myabstractions]))
(defn testFoo [myFoo]
(let [fooResult (foo myFoo)]
(...some expression that returns a boolean...)))
(defn testBar [myBar]
(let [barResult (bar myBar)]
(...some expression that returns a boolean...)))从您对每个实现的特定测试中,您只需要传递实现该协议的记录实例。
https://stackoverflow.com/questions/16578882
复制相似问题