Я столкнулся с довольно странной проблемой закрытия, связанной с модульным тестированием spock, и подумал, может ли кто-нибудь объяснить это.
Если мы представим себе дао, модель и сервис следующим образом:
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 своим макетом, проверяю, что dao правильно вызывается и возвращаю правильные данные, а затем проверяю, что получаю правильный результат (т.е. «and:
»).
Сложная часть - это цикл for, в котором я хотел вернуть модели из mock dao, связанные с параметром метода. В приведенном выше примере, если я использую простой for (__ in idSet)
, модели возвращаются только с идентификатором 10: outputSet.get(0).getId() == outputSet.get(1).getId() == 10
. Если я использую традиционный цикл for и устанавливаю модель с idSet.get(i)
, я получаю IndexOutOfBoundsException
. Единственный способ выполнить эту работу - получить значение в локальной переменной (id
) и установить с помощью переменной, как указано выше.
Я знаю, что это связано с классными замыканиями, и подозреваю, что spock захватывает фиктивные вызовы в набор замыканий перед их выполнением, а это означает, что создание модели зависит от внешнего состояния замыкания. Я понимаю, почему я получил исключение IndexOutOfBoundsException, но не понимаю, почему int id = idSet.get(i)
захватывается закрытием, а i
- нет.
В чем разница?
Примечание: это не живой код, а скорее упрощенный, чтобы продемонстрировать суть моей задачи. Я бы не стал и не буду делать два последовательных вызова dao для getIds () и getModelById ().