Тестовое приложение с основными данными

Как я должен проверить findByAttribute метод экземпляра, который я добавил к NSManagedObject?

Сначала я подумал о программном создании независимого стека базовых данных, как продемонстрировано в учебнике по основным данным XCode. И в процессе поиска этой документации я наткнулся на шаблоны запросов на получение основных данных и подумал, что, возможно, вместо создания метода, который я создал, я должен создать шаблоны запросов на выборку, но это не похоже на entityName может быть переменной с шаблоном запроса выборки, не так ли? Могу ли я создать шаблон запроса на получение NSManagedObject чтобы все подклассы могли его использовать? Хм, но тогда мне все равно нужен entityName и я не думаю, что есть способ динамически получить имя подкласса, который вызвал метод.

В любом случае, похоже, что хорошим решением является создание стека CoreData в памяти для тестирования, независимого от производственного стека CoreData. @Jeff Schilling также рекомендует создать постоянное хранилище в памяти. Крис Хэнсон также создает постоянный координатор магазина для модульного тестирования CoreData. Это похоже на то, как у Rails есть отдельная база данных для тестирования. Но @iamleeg рекомендует убрать зависимость CoreData.

Как вы думаете, какой подход лучше? Я лично предпочитаю последнее.

ОБНОВЛЕНИЕ: я тестирую базовые данные с помощью OCHamcrest и Cedar из Pivotal Lab. В дополнение к написанию кода ниже, я добавил NSManagedObject+Additions.m а также User.m к Spec цель.

#define HC_SHORTHAND
#import <Cedar-iPhone/SpecHelper.h>
#import <OCHamcrestIOS/OCHamcrestIOS.h>

#import "NSManagedObject+Additions.h"
#import "User.h"

SPEC_BEGIN(NSManagedObjectAdditionsSpec)

describe(@"NSManagedObject+Additions", ^{
    __block NSManagedObjectContext *managedObjectContext;   

    beforeEach(^{
        NSManagedObjectModel *managedObjectModel =
                [NSManagedObjectModel mergedModelFromBundles:nil];

        NSPersistentStoreCoordinator *persistentStoreCoordinator =
                [[NSPersistentStoreCoordinator alloc]
                 initWithManagedObjectModel:managedObjectModel];

        [persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
                                                 configuration:nil URL:nil options:nil error:NULL];

        managedObjectContext = [[NSManagedObjectContext alloc] init];
        managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator;

        [persistentStoreCoordinator release];
    });

    it(@"finds first object by attribute value", ^{

        // Create a user with an arbitrary Facebook user ID.
        NSNumber *fbId = [[NSNumber alloc] initWithInteger:514417];
        [[NSEntityDescription insertNewObjectForEntityForName:@"User"
                                      inManagedObjectContext:managedObjectContext] setFbId:fbId];
        [managedObjectContext save:nil];

        NSNumber *fbIdFound = [(User *)[User findByAttribute:@"fbId" value:(id)fbId
                                                  entityName:@"User"
                                      inManagedObjectContext:managedObjectContext] fbId];

        assertThatInteger([fbId integerValue], equalToInteger([fbIdFound integerValue]));

        [fbId release];
    });

    afterEach(^{
        [managedObjectContext release];
    }); 
});

SPEC_END

Если вы можете сказать мне, почему, если я не брошу (id) fbId аргумент передан findByAttribute я получил

warning: incompatible Objective-C types 'struct NSNumber *',
expected 'struct NSString *' when passing argument 2 of
'findByAttribute:value:entityName:inManagedObjectContext:' from
distinct Objective-C type

тогда вы получаете бонусные баллы!:) Кажется, я не должен был бросать NSNumber для id если аргумент должен быть id так как NSNumber является id, право?

1 ответ

Решение

Моя личная философия заключается в том, что тест не является тестом, если он не тестирует реальную вещь, поэтому я смотрю косо на любой метод, который тестирует фрагменты изолированно. Хотя он будет работать во многих случаях, особенно в процедурном коде, он может дать сбой в сложном коде, например, в графах объектов Core Data.

Большинство точек отказа в Базовых данных происходят из-за плохой модели данных, например, из-за отсутствия взаимных отношений, так что график выходит из равновесия, и у вас появляются потерянные объекты. Единственный способ проверить плохой граф - это создать известный граф, а затем провести стресс-тестирование вашего кода, чтобы посмотреть, сможет ли он найти объекты в графе и манипулировать ими.

Для реализации этого типа теста я делаю следующее:

  1. Запустите каждый тестовый прогон, удалив ранее существовавший файл хранилища Core Data, чтобы хранилище всегда начиналось в известном состоянии.
  2. Создайте новое хранилище для каждого прогона, желательно, каждый раз генерируя его в коде, но вы можете просто поменять копию файла хранилища перед каждым прогоном. Я предпочитаю первый метод, потому что он на самом деле легче в долгосрочной перспективе.
  3. Убедитесь, что данные теста содержат относительно экстремальные примеры, например длинные имена, строки с символами мусора, очень большие и очень маленькие цифры и т. Д.

Состояние графа тестового объекта должно быть абсолютно известно во время каждого теста. Я регулярно выкидываю весь граф в тестирование, и у меня есть методы для детального вывода как сущностей, так и живых объектов.

Я обычно разрабатываю и тестирую всю модель данных приложения в отдельной настройке проекта приложения, чтобы заниматься только разработкой модели данных. Только после того, как модель данных работает точно так, как необходимо в приложении, я перемещаю ее в полный проект и начинаю добавлять контроллеры и интерфейс.

Поскольку модель данных является фактическим ядром правильно реализованного приложения для проектирования Model-View-Controller, правильная модель данных покрывает 50-75% разработки. Остальное - прогулка по пирогу.

В этом конкретном случае вам действительно нужно только проверить, что предикат запроса на выборку возвращает правильные объекты. Единственный способ проверить это - предоставить ему полный график тестирования.

(Я хотел бы отметить, что этот метод на самом деле довольно бесполезен на практике. Он не будет возвращать какой-либо конкретный объект по атрибуту, а просто любой из произвольного числа объектов, имеющих атрибут этого значения. Например, если у вас есть граф объектов с 23462 Person объекты с firstName значение атрибута Johnэтот метод вернет ровно одну произвольную сущность Person из 23 462. Я не вижу смысла в этом. Я думаю, что вы думаете с точки зрения процедурного SQL. Это приведет к путанице при работе с диспетчером объектных графиков, таким как Core Data.)

Обновить:

Я собираюсь догадаться, что ваша ошибка вызвана тем, что компилятор смотрит на использование value в предикате и предполагая, что это должен быть объект NSString. Когда вы отбрасываете объект в строковом формате, например, используемый predicateWithFormat:фактическое возвращаемое значение является объектом NSString, содержащим результаты description метод объекта. Итак, для компилятора, который вы предикат, на самом деле выглядит так:

[NSPredicate predicateWithFormat:@"%K == %@", (NSString *)attribute, (NSString *)value]

... поэтому, когда он работает в обратном направлении, он будет искать NSString в value Параметр, хотя технически это не должно. Такое использование идентификатора на самом деле не лучшая практика, потому что оно будет принимать любой класс, но вы не всегда знаете, какая строка описания возвращается экземпляром. -description метод будет.

Как я уже говорил выше, у вас есть некоторые концептуальные проблемы здесь. Когда вы говорите в комментарии ниже:

Я хотел создать метод, аналогичный динамическому поиску Active_ecord find_by_.

... вы подходите к базовым данным с неправильной точки зрения. Active Record в значительной степени является оберткой объектов для SQL, чтобы упростить интеграцию существующих серверов SQL с Ruby on Rails. В этом качестве преобладают процедурные концепции SQL.

Это совершенно противоположный подход, используемый Core Data. Core Data - это, прежде всего, система управления графами объектов для создания слоев модели в проекте приложения Model-View-Controller. Таким образом, объекты являются всем. Например, можно даже иметь объекты без атрибутов, только отношения. Такие объекты также могут иметь очень сложное поведение. Это то, чего на самом деле не существует в SQL или даже в активной записи.

Вполне возможно иметь произвольное количество объектов с одинаковыми атрибутами. Это делает метод, который вы пытаетесь создать, бесполезным и опасным, потому что вы никогда не будете знать, какой объект вы получите. Это делает его "хаотичным" методом. Если у вас есть несколько объектов с одним и тем же атрибутом, метод будет произвольно возвращать любой отдельный объект, который соответствует значению атрибута.

Если вы хотите идентифицировать конкретный объект, вам нужно захватить объект ManagedObjectID а затем использовать -[NSManagedObjectContext objectForID:] чтобы получить это. Как только объект был сохранен, его ManagedObjectID уникален

Однако эта функция обычно используется только тогда, когда вам приходится ссылаться на объекты в разных магазинах или даже в разных приложениях. В противном случае нет никакого смысла. При использовании базовых данных вы ищете объекты, основанные не только на их атрибутах, но и на их положении, то есть на их отношении к другим объектам в графе объектов.

Позвольте мне скопировать и вставить несколько очень важных советов: Базовые данные - это не SQL. Сущности не являются таблицами. Объекты не являются строками. Столбцы не являются атрибутами. Базовые данные - это система управления графом объектов, которая может сохранять или не сохранять граф объекта и может использовать или не использовать SQL для этого далеко за кулисами. Попытка представить базовые данные в терминах SQL приведет к тому, что вы полностью неправильно поймете базовые данные и приведет к большим трудностям и потерянному времени.

Естественно попытаться запрограммировать новый API, используя конструкции API, с которыми вы уже знакомы, но это опасная ловушка, когда новый API имеет принципиально отличную философию проектирования от старого API.

Если вы обнаружите, что пытаетесь написать базовую и основную функцию старого API в новом, это само по себе должно предупредить вас, что вы не синхронизированы с новой философией API. В этом случае вы должны спросить, почему, если общий findByAttribute метод был полезен в Core Data, почему Apple его не поставил? Не более ли вероятно, что вы пропустили важную концепцию в Core Data?

Другие вопросы по тегам