Groovy Spock单元测试和内部关闭

Groovy Spock单元测试和内部关闭,groovy,closures,spock,Groovy,Closures,Spock,我遇到了一个与spock单元测试相关的相当奇怪的关闭问题,我想知道是否有人可以解释这一点 如果我们设想一个dao、模型和服务如下: interface CustomDao { List<Integer> getIds(); Model getModelById(int id); } class CustomModel { int id; } class CustomService { CustomDao customDao public List<Object> c

我遇到了一个与spock单元测试相关的相当奇怪的关闭问题,我想知道是否有人可以解释这一点

如果我们设想一个dao、模型和服务如下:

interface CustomDao {
List<Integer> getIds();
Model getModelById(int id);
}

class CustomModel {
int id;
}

class CustomService {
CustomDao customDao

public List<Object> createOutputSet() {
    List<Model> models = new ArrayList<Model>();
    List<Integer> ids = customDao.getIds();
    for (Integer id in ids) {
        models.add(customDao.getModelById(id));
    }
    return models;
}
}
接口自定义DAO{
列出getid();
模型getModelById(int-id);
}
类自定义模型{
int-id;
}
类客户服务{
CustomDao CustomDao
公共列表createOutputSet(){
列表模型=新的ArrayList();
List id=customDao.getIds();
for(id中的整数id){
add(customDao.getModelById(id));
}
收益模型;
}
}
我想对CustomService.createOutputSet进行单元测试。我创建了以下规范:

class TestSpec extends Specification {

def 'crazy closures'() {
    def mockDao = Mock(CustomDao)
    def idSet = [9,10]

    given: 'An initialized object'
        def customService = new CustomService
        customService.customDao = mockDao

    when: 'createOutput is called'
        def outputSet = customService.createOutputSet()

    then: 'the following methods should be called'
        1*mockDao.getIds() >> {
            return idSet
        }

        for (int i=0; i<idSet.size(); i++) {
            int id = idSet.get(i)
            1*mockDao.getModelById(idSet.get(i)) >> {
                def tmp = new Model()
                int tmpId = id // idSet.get(i)
                return tmp
            }
        }

    and: 'each compute package is accurate'
        2 == outputSet.size()
        9 == outputSet.get(0).getId()
        10 == outputSet.get(1).getId()

}
}
类TestSpec扩展了规范{
def“疯狂闭包”(){
def mockDao=Mock(CustomDao)
def idSet=[9,10]
给定:“初始化对象”
def customService=新的customService
customService.customDao=mockDao
当调用“createOutput”时
def outputSet=customService.createOutputSet()
然后:“应调用以下方法”
1*mockDao.getIds()>>{
返回idSet
}
对于(int i=0;i>{
def tmp=新型号()
int tmpId=id//idSet.get(i)
返回tmp
}
}
并且:“每个计算包都是精确的”
2==outputSet.size()
9==outputSet.get(0.getId)()
10==outputSet.get(1.getId)()
}
}
注意,在这里我测试了两件事。首先,我用mock初始化dao,验证dao是否被正确调用并返回正确的数据,然后验证我是否得到正确的输出(即“
和:
”)

棘手的部分是for循环,在该循环中,我希望从模拟dao返回与方法参数相关的模型。在上面的示例中,如果我使用一个简单的
for(u uuu-in-idSet)
,模型只返回id为10:
outputSet.get(0).getId()==outputSet.get(1).getId()==10
。如果我使用传统的for循环,并使用
idSet.get(I)
设置模型,我会得到一个
IndexOutOfBoundsException
。实现此功能的唯一方法是检索局部变量(
id
)中的值并使用变量进行设置,如上所述

我知道这与groovy闭包有关,我怀疑spock在执行之前会将模拟调用捕获到一组闭包中,这意味着模型的创建取决于闭包的外部状态。我理解为什么会得到IndexOutOfBoundsException,但我不理解为什么
int id=idSet.get(I)
被闭包捕获,而
i
不是

有什么区别


注意:这不是实时代码,而是为了演示我的挑战的关键而简化的代码。我不会也不会对getIds()和getModelById()进行两个后续dao调用

for (int i=0; i<idSet.size(); i++) {
            //int id = idSet.get(i)
            mockDao.getModelById(idSet.get(i)) >> {int id ->
                def tmp = new Model()
                tmp.id = id // id is closure param which represents idSet.get(i)
                return tmp
            }
        }

我们是否需要担心如果方法被存根,它会被调用多少次?

当通过闭包存根
getModelById
时,闭包的参数必须与方法的参数匹配。如果您尝试下面的方法,您将不再需要
内部的局部变量
id

for (int i=0; i<idSet.size(); i++) {
            //int id = idSet.get(i)
            mockDao.getModelById(idSet.get(i)) >> {int id ->
                def tmp = new Model()
                tmp.id = id // id is closure param which represents idSet.get(i)
                return tmp
            }
        }

如果方法被存根,我们需要担心调用了多少次吗?

从延迟执行的闭包访问可变局部变量是常见的错误源,而不是Spock特有的

我不明白为什么int id=idSet.get(I)被闭包捕获,而我不是

前者在每次迭代中产生一个单独的提升变量,其值为常量。后者产生一个单独的提升变量,其值随时间变化(在结果生成器执行之前)

不是通过引入临时变量来解决问题,而是更好的解决方案(已由@dmahapatro给出)是声明一个
int id->
closure参数。如果认为它足够好,可以在不强制执行调用的情况下存根调用,则可以完全忽略循环。另一个潜在的解决方案是急切地构造返回值:

idSet.each { id ->
    def model = new Model()
    model.id = id
    1 * mockDao.getModelById(id) >> model
}

从延迟执行的闭包访问可变局部变量是常见的错误源,而不是Spock特有的

我不明白为什么int id=idSet.get(I)被闭包捕获,而我不是

前者在每次迭代中产生一个单独的提升变量,其值为常量。后者产生一个单独的提升变量,其值随时间变化(在结果生成器执行之前)

不是通过引入临时变量来解决问题,而是更好的解决方案(已由@dmahapatro给出)是声明一个
int id->
closure参数。如果认为它足够好,可以在不强制执行调用的情况下存根调用,则可以完全忽略循环。另一个潜在的解决方案是急切地构造返回值:

idSet.each { id ->
    def model = new Model()
    model.id = id
    1 * mockDao.getModelById(id) >> model
}

为什么不使用>>>来控制getModelById mock返回的值?这似乎是一种更简洁的方法来完成您尝试执行的操作。除非我遗漏了某些内容,否则这基本上就是我正在做的。请注意,对于getModelById,我使用右移运算符返回一个依赖于其输入的值。通过这种方法,我正在测试交互dao。对于给定的每个id,我希望它随后会使用该参数调用并返回一个与其相关的对象。在我的系统代码中,我测试了进一步的交互,这使得在评估方法返回的包列表时具有清晰可分辨的状态非常重要。为什么不使用>>>来控制getModelById mock?返回的值似乎是一种更简洁的方法