Spock单元测试和内部闭合

时间:2013-06-30 01:28:16

标签: 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> createOutputSet() {
    List<Model> models = new ArrayList<Model>();
    List<Integer> ids = customDao.getIds();
    for (Integer id in ids) {
        models.add(customDao.getModelById(id));
    }
    return models;
}
}

我想对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()

}
}

请注意,在这里我测试了两件事。首先,我用模拟初始化dao,验证是否正确调用了daos并返回正确的数据,然后我验证我得到了正确的输出(即“and:”)。

棘手的部分是for循环,我想从模拟dao返回与method参数相关的模型。在上面的示例中,如果我使用简单的for (__ 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调用。

2 个答案:

答案 0 :(得分:2)

虽然通过闭包来存根getModelById,但闭包的参数必须与方法的参数匹配。如果您尝试以下内容,则不再需要id内的局部变量for

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
            }
        }

简化版将使用each

idSet.each {
    mockDao.getModelById(it) >> {int id ->
        def tmp = new Model()
        tmp.id = id // id is closure param which represents idSet.get(i)
        tmp
    }
}

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

答案 1 :(得分:2)

从延迟执行的闭包中访问可变局部变量是Spock不常见的常见错误来源。

  

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

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

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

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