2011-03-18 18 views
5

¿Cómo debo probar el findByAttribute instance method I added to NSManagedObject?Prueba Core Data Application

Al principio, pensé en programmatically creating an independent Core Data stack as demonstrated by Xcode's Core Data Utility Tutorial. Y, en mi búsqueda de esa documentación, me encontré con Core Data Fetch Request Templates y pensé que tal vez en lugar de crear el método que hice, debería hacer plantillas de solicitud de recuperación, pero no parece que el entityName puede ser variable con una plantilla de solicitud de recuperación. ¿puede? ¿Puedo crear una plantilla de solicitud de recuperación en NSManagedObject para que todas las subclases puedan usarla? Hmm, pero aún necesitaría un entityName y no creo que haya una manera de obtener dinámicamente el nombre de la subclase que llamó al método.

De todos modos, parece que una buena solución es create an in-memory Core Data stack for testing, independiente de la pila de datos básicos de producción. @Jeff Schilling also recommends creating an in-memory persistent store. Chris Hanson also creates a persistent store coordinator to unit test Core Data. Esto parece similar a cómo Rails tiene una base de datos separada para probar. Pero, @iamleeg recommends removing the Core Data dependence.

¿Cuál crees que es el mejor enfoque? Yo personalmente prefiero lo último.

ACTUALIZACIÓN: Estoy unit testing Core Data with OCHamcrest and Pivotal Lab's Cedar. Además de escribir el código a continuación, agregué NSManagedObject+Additions.m y User.m al objetivo 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 

Si me puede decir por qué si no lo hago me sale cast to (id) the fbId argument passed to 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 

entonces obtendrá puntos de bonificación! :) Parece que no debería tener que lanzar un NSNumber a un id si se supone que el argumento es un id porque NSNumber es un id, ¿verdad?

+0

+1 Una pregunta bien escrita y documentada. Gracias por tomarse el tiempo para componerlo. – TechZen

+0

¡Gracias y de nada! – ma11hew28

+0

¿Qué línea es 47? Sus números de línea no aparecen en el código de la publicación. ;-) – TechZen

Respuesta

4

Mi filosofía personal es que una prueba no es una prueba si no prueba la realidad así que miro con recelo cualquier método que pruebe fragmentos en forma aislada. Aunque funcionará en muchos casos, especialmente en el código de procedimiento, es probable que falle en un código complejo como el que se encuentra en los gráficos de objetos de Core Data.

La mayoría de los puntos de error en los datos principales provienen de un modelo de datos incorrecto, p. falta una relación recíproca para que el gráfico salga de balance y tengas objetos huérfanos. La única manera de probar un gráfico malo es crear un gráfico conocido y luego probar su código para comprobar si puede encontrar y manipular los objetos en el gráfico.

Para poner en práctica este tipo de prueba que haga lo siguiente:

  1. inicio de cada prueba ejecutar eliminando el archivo de almacén de datos básicos previamente existente, que la tienda siempre comienza en un estado conocido.
  2. Proporcione un nuevo almacén para cada ejecución, preferentemente generando cada código en código pero puede simplemente intercambiar una copia del archivo de tienda antes de cada ejecución. Prefiero el método anterior porque es realmente más fácil a largo plazo.
  3. Asegúrese de que los datos de prueba contengan ejemplos relativamente extremos, p. nombres largos, cadenas con caracteres basura, números muy grandes y muy pequeños, etc.

El estado del gráfico del objeto de prueba debe conocerse completamente en el momento de cada prueba. De forma rutinaria vuelco todo el gráfico en las pruebas y tengo métodos para volcar las entidades y los objetos en vivo en detalle.

Normalmente desarrollo y pruebo el modelo de datos completo de una aplicación en una configuración de proyecto de aplicación separada para no hacer nada más que desarrollar el modelo de datos. Solo cuando tengo el modelo de datos funcionando exactamente como se necesita en la aplicación, lo muevo al proyecto completo y empiezo a agregar controladores e interfaz.

Dado que el modelo de datos es el núcleo real de una aplicación de diseño Model-View-Controller correctamente implementada, obtener el modelo de datos correcto cubre% 50-% 75 de desarrollo. El resto es una caminata de pastel.

En este caso particular, solo necesita probar que el predicado de la solicitud de recuperación devuelve los objetos adecuados. La única forma de probar eso es proporcionarle un gráfico de prueba completo.

(Me gustaría señalar que este método es realmente bastante inútil en la práctica. No devolverá ningún objeto en particular por atributo sino simplemente cualquiera de un número arbitrario de objetos que tengan un atributo de ese valor. Por ejemplo, si tiene un objeto gráfico con 23,462 Person objetos con el valor de atributo firstName de John, este método devolverá exactamente una entidad de persona arbitraria fuera de 23,462. No veo el punto de esto. Creo que está pensando en términos de procedimientos de SQL. a la confusión cuando se trata de un administrador de objetos -graph como datos básicos)

actualización:.

Voy a adivinar que su error es causado por el compilador que mira el uso de value en el predicado y suponiendo que debe ser un objeto NSString. Cuando suelta un objeto en un formato de cadena, como el utilizado por predicateWithFormat:, el valor real devuelto es un objeto NSString que contiene los resultados del método description del objeto. Por lo tanto, al compilador que predicado en realidad se parece a esto:

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

... así que cuando funciona al revés que estará buscando un NSString en el parámetro value a pesar de que técnicamente no debería. Este uso de la identificación no es realmente una buena práctica porque aceptará cualquier clase, pero en realidad no siempre sabes cuál será la cadena de descripción devuelta por el método de la instancia -description.

Como dije anteriormente, usted tiene algunos problemas conceptuales aquí. Cuando se dice en el comentario a continuación:

Mi intención era hacer un método análogo al find_by_ buscador dinámico de ActiveRecord.

... se está acercando a los datos centrales desde la perspectiva equivocada. Active Record es principalmente un contenedor de objetos alrededor de SQL para facilitar la integración de servidores SQL existentes con Ruby on Rails. Como tal, está dominado por conceptos de SQL de procedimiento.

Ese es el enfoque exactamente opuesto utilizado por Core Data. Core Data es, ante todo, un sistema de gestión de gráficos por objetos para crear las capas modelo de un diseño de aplicación Model-View-Controller. Como tal, los objetos son todo. P.ej. Incluso es posible tener objetos sin atributos, solo relaciones. Tales objetos pueden tener comportamientos muy complejos también. Eso es algo que realmente no existe en SQL o incluso en el registro activo.

Es muy posible tener un número arbitrario de objetos con exactamente los mismos atributos. Esto hace que el método que estás tratando de crear sea inútil y peligroso porque nunca sabrás qué objeto recibirás. Eso lo convierte en un método "caótico".Si tiene varios objetos con el mismo atributo, el método devolverá arbitrariamente cualquier objeto individual que coincida con el valor de atributo proporcionado.

Si desea identificar un objeto en particular, debe capturar el objeto ManagedObjectID y luego usar -[NSManagedObjectContext objectForID:] para recuperarlo. Una vez que se ha guardado un objeto, su ManagedObjectID es único.

Sin embargo, esta característica solo se usa cuando se debe hacer referencia a objetos en diferentes tiendas o incluso en diferentes aplicaciones. Por lo general, no tiene sentido lo contrario. Al utilizar los datos centrales, busca objetos basados ​​no solo en sus atributos sino también en su posición, es decir, su relación con otros objetos, en el gráfico de objetos.

Permítanme copiar y pegar algunos consejos muy importantes: Core Data no es SQL. Las entidades no son tablas. Los objetos no son filas. Las columnas no son atributos. Core Data es un sistema de gestión de gráficos de objeto que puede o no persistir en el gráfico de objetos y puede o no usar SQL muy detrás de escena para hacerlo. Tratar de pensar en los Datos Básicos en términos de SQL hará que malinterprete completamente los Datos Básicos y resultará en mucho dolor y pérdida de tiempo.

Es natural probar y programar una nueva API usando los diseños de una API con la que ya está familiarizado pero es una trampa peligrosa cuando la nueva API tiene una filosofía de diseño sustancialmente diferente de la antigua API.

Si intenta escribir una función básica y funcional de la antigua API en la nueva, solo eso debería advertirle que no está sincronizado con la nueva filosofía de API. En este caso, deberías preguntarte por qué si un método genérico findByAttribute fue útil en Core Data, ¿por qué Apple no proporcionó uno? ¿No es más probable que hayas olvidado un concepto importante en Core Data?

+0

¡Gracias por la excelente respuesta! Seguí tu consejo y algunos de los enlaces en mi pregunta para encontrar una solución, agregué a mi pregunta. Además, 'findByAttribute' es principalmente para encontrar objetos gestionados por un atributo único. Mi intención era hacer un método análogo al [buscador dinámico 'find_by_' de ActiveRecord] (http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders). Tal vez, debería darle un descriptor de ordenación, pero entonces, ¿qué ordenaría? Quizás termine también implementando 'find_all_by_', y luego' find_by' sería simplemente un contenedor de 'find_all_by_' con limit = 1. – ma11hew28