Странный порядок блоков контекста Kiwi iOS

У меня есть файл спецификации Kiwi, который выглядит примерно так:

#import "Kiwi.h"
#import "MyCollection.h"

SPEC_BEGIN(CollectionSpec)

describe(@"Collection starting with no objects", ^{
    MyCollection *collection = [MyCollection new];

    context(@"then adding 1 object", ^{
        MyObject *object = [MyObject new];
        [collection addObject:object];
        it(@"has 1 object", ^{
            [collection shouldNotBeNil];
            [collection.objects shouldNotBeNil];
            [[theValue(collection.objects.count) should] equal:theValue(1)]; //failing test
        });

        context(@"then removing 1 object", ^{
            [collection removeObject:object];
            it(@"has 0 objects", ^{
                [[theValue(collection.objects.count) should] equal:theValue(0)]; //passing test
            });
        });
    });
});

SPEC_END

Запуск спецификации приводит к одному сбою в этой строке кода [[theValue(collection.objects.count) should] equal:theValue(1)];

Вот что странно: если я удалю весь блок context(@"then removing 1 object", ^{...}) из спецификации, вышеупомянутый тест пройдет.

Это наводит меня на мысль, что строка [collection removeObject:object] выполняется до неудачного теста. У меня такое чувство, что я могу неправильно понимать порядок, в котором выполняются блоки.

Мы ценим любые предложения!


person Barjavel    schedule 12.04.2013    source источник
comment
вам не нужно __block на collection или object, так как вы нигде не назначаете ни одному   -  person newacct    schedule 13.04.2013
comment
Вы правы - я отредактировал свой вопрос. Когда я меняю это, я все равно вижу то же поведение.   -  person Barjavel    schedule 13.04.2013
comment
я мало что знаю о киви, но вы уверены, что [collection shouldNotBeNil]; правильно? если collection это nil то это просто не будет   -  person Bryan Chen    schedule 13.04.2013
comment
shouldNotBeNil — стандартная часть библиотеки Kiwi, но я вижу, как это может ввести в заблуждение. Обратите внимание, что это не обычный вызов метода — на самом деле это макрос, который в конечном итоге добавляет ожидаемый тип к объекту спецификации. Таким образом, на самом деле это не передача сообщения потенциально нулевому объекту. Вы можете проверить весь исходный код Kiwi здесь, если хотите посмотреть поближе? - github.com/allending/Kiwi   -  person Barjavel    schedule 13.04.2013
comment
Правильно, shouldNotBeNil — это специальный макрос, который будет делать правильные вещи. Также обратите внимание, что более типичные макросы should и shouldNot, которые используются для большинства ожиданий киви, неявно устанавливают ожидание того, что субъект на самом деле не nil (в дополнение к фактическому ожиданию, которое вы настраиваете, например, shouldEqual:something).   -  person Mike Mertsock    schedule 15.04.2013
comment
Кроме того, вы можете исключить свои shouldNotBeNil проверки и заменить проверки количества более простыми: [[collection.objects should] haveCountOf:integer]. Сообщение haveCountOf: не требует использования theValue, вы передаете ему скалярное целое число. Поскольку should будет неявно утверждать, что collection.objects не является nil (и, таким образом, также потерпит неудачу, если collection само равно nil), можно безопасно сделать это без отдельного утверждения shouldNotBeNil, даже если вы проверяете количество нулей.   -  person Mike Mertsock    schedule 15.04.2013


Ответы (1)


Вы правы, что [collection removeObject:object] выполняется до неудачного теста. Представьте, что тесты Kiwi работают в два прохода:

  1. Настройка: код в вашем тестовом файле выполняется сверху вниз, чтобы настроить тестовые контексты и ожидания.
  2. Выполнение: запускается каждый модульный тест, в основном по одному на оператор it/specify, с повторением правильного кода установки/разборки для каждого теста.

Обратите внимание, что большая часть кода в тестовом файле Kiwi указана в виде серии блоков, отправляемых функциям Kiwi. Таким образом, любой код, который не соответствует шаблонам блоков Kiwi, например ваш код, который инициализирует/изменяет переменную collection, может выполниться в неожиданное время. В этом случае весь код модификации коллекции выполняется во время первого прохода при настройке тестов, а затем запускаются ваши тесты.

Решение

Объявите collection с модификатором __block и используйте beforeEach для создания экземпляра и изменения объекта collection:

describe(@"Collection starting with no objects", ^{
    __block MyCollection *collection;
    beforeEach(^{
        collection = [MyCollection new];
    });

    context(@"then adding 1 object", ^{
        beforeEach(^{
            MyObject *object = [MyObject new];
            [collection addObject:object];
        });
        it(@"has 1 object", ^{
            ...
        });

        context(@"then removing 1 object", ^{
            beforeEach(^{
                [collection removeObject:object];
            });
            it(@"has 0 objects", ^{
                ...

Блоки beforeEach говорят Kiwi специально запускать данный код один раз для модульного теста, а с вложенными контекстами блоки будут выполняться в нужной последовательности. Итак, Kiwi сделает что-то вроде этого:

// run beforeEach "Collection starting with no objects"
collection = [MyCollection new]
// run beforeEach "then adding 1 object"
MyObject *object = [MyObject new]
[collection addObject:object]
// execute test "has 1 object"
[collection shouldNotBeNil]
[collection.objects shouldNotBeNil]
[[theValue(collection.objects.count) should] equal:theValue(1)]

// run beforeEach "Collection starting with no objects"
collection = [MyCollection new]
// run beforeEach "then adding 1 object"
MyObject *object = [MyObject new]
[collection addObject:object]
// run beforeEach "then removing 1 object"
[collection removeObject:object]
// execute test "has 0 objects"
[[theValue(collection.objects.count) should] equal:theValue(0)]

Модификатор __block гарантирует, что ссылка на объект collection может быть правильно сохранена и изменена во всех этих функциях блока.

person Mike Mertsock    schedule 14.04.2013
comment
Это объяснение действительно помогло! Теперь я думаю о блоках context/describe только как о логических разделителях, при этом любой код должен находиться в каком-либо другом типе блока Kiwi. Спасибо за всю информацию - очень признателен! - person Barjavel; 15.04.2013