• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Bienvenido a la republica independiente de las pruebas unitarias con Core Data
 

Bienvenido a la republica independiente de las pruebas unitarias con Core Data

on

  • 1,381 views

Presentación para el NSCoder Night Madrid del 6 de marzo de 2013 por Jorge Ortiz y yo mismo. En esta presentación hablado de cómo se pueden hacer pruebas unitarias a un modelo en Core Data y cómo ...

Presentación para el NSCoder Night Madrid del 6 de marzo de 2013 por Jorge Ortiz y yo mismo. En esta presentación hablado de cómo se pueden hacer pruebas unitarias a un modelo en Core Data y cómo conseguir desacoplar este modelo de la forma en que hemos elegido que persista.

Statistics

Views

Total Views
1,381
Views on SlideShare
479
Embed Views
902

Actions

Likes
0
Downloads
4
Comments
0

1 Embed 902

http://nscoder-mad.tumblr.com 902

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Bienvenido a la republica independiente de las pruebas unitarias con Core Data Bienvenido a la republica independiente de las pruebas unitarias con Core Data Presentation Transcript

    • Bienvenido a la república independiente de las pruebas unitarias con Core Data Jorge D. Ortiz Fuentes (@jdortiz) Alfonso Alba Garcia (@aalbagarcia)viernes, 8 de marzo de 13
    • Agenda ★ MVC ★ Implementación en Core Data ★ Pruebas Unitarias ★ Desacoplamiento ★ Conclusiones 2viernes, 8 de marzo de 13
    • El modelo a seguirviernes, 8 de marzo de 13
    • MVC ★ Las vistas las proporciona Apple (aunque nosotros podemos crear lasque necesitemos). ★ El modelo debe contener toda la lógica de negocio. ★ Atención: Modelo de datos vs modelo de negocio. ★ El controlador debería la conexión de las vistas con el modelo de negocio. ★ No es necesario que sea / NO debería ser un singleton. Se pasa de un controlador a otro. (Core Data: MOC o UIManagedDocument) 4viernes, 8 de marzo de 13
    • Modelo autocontenido ★ La forma más sencilla de evitar duplicación de código y conseguir un comportamiento consistente. ★ Core Data incluye restricciones. P. ej., atributo opcional o no o cardinalidad de una relación. ★ Pero para añadir otra funcionalidad hay que añadir métodos. 5viernes, 8 de marzo de 13
    • Usa Core Data, Lukeviernes, 8 de marzo de 13
    • Implementación del modelo de negocio ★ Modificar modelo de datos ⇒ regenerar clases. Xcode sobreescribe ⇒ métodos añadidos ★ Soluciones: ๏ Aprovechar el control de versiones ๏ mogenerator (http:// rentzsch.github.com/mogenerator/) de W. Rentzsch ๏ Categorías 7viernes, 8 de marzo de 13
    • Implementación del modelo de negocio ★ Modificar modelo de datos ⇒ regenerar clases. Xcode sobreescribe ⇒ métodos añadidos ★ Soluciones: ๏ Aprovechar el control de versiones ๏ mogenerator (http:// rentzsch.github.com/mogenerator/) de W. Rentzsch ๏ Categorías 7viernes, 8 de marzo de 13
    • Modelo NSCoderAppviernes, 8 de marzo de 13
    • Group (Generado) ★ #import <Foundation/Foundation.h> #import "Group.h" #import "Person.h" #import <CoreData/CoreData.h> @implementation Group @class Person; @dynamic name; @interface Group : NSManagedObject @dynamic notes; @dynamic members; @property (nonatomic, retain) NSString * name; @dynamic meetings; @property (nonatomic, retain) NSString * notes; @property (nonatomic, retain) NSSet *members; @end @property (nonatomic, retain) NSManagedObject *meetings; @end @interface Group (CoreDataGeneratedAccessors) - (void)addMembersObject:(Person *)value; - (void)removeMembersObject:(Person *)value; - (void)addMembers:(NSSet *)values; - (void)removeMembers:(NSSet *)values; @endviernes, 8 de marzo de 13
    • Categoría Group+Model ★ #import "Group.h" #import "Group+Model.h" @implementation Group (Model) #pragma mark - Detect duplicates @interface Group (Model) /** - (BOOL) isDuplicated; Verify that this item doesnt exist yet (another section with the same name). @end */ - (BOOL) isDuplicated { BOOL duplicated = NO; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Group"]; // Set the predicate to find if another one exists. fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", self.name]; NSError *error = nil; NSUInteger sections = [self.managedObjectContext countForFetchRequest:fetchRequest error:&error]; // The first one is the one this one. if (sections > 1) { duplicated = YES; } return duplicated; } @endviernes, 8 de marzo de 13
    • Pruebasviernes, 8 de marzo de 13
    • La prueba del 3 ★ Incluir pruebas: ๏ Más exhaustivo y sistemático. ๏ Refactorizar con mucho menos riesgo. ๏ Verificar la resolución de bugs y evitar regresiones. ★ Lo que proporciona el sistema (frameworks) no se prueba. ★ Como mínimo: ๏ Modelo de negocio → Métodos añadidos 12viernes, 8 de marzo de 13
    • Implementación de las pruebas ★ OCUnit para no compicarse la vida ★ Nombre explicativo ★ Cobertura ★ Casos relevantes ★ Importante para Core Data: setUp y tearDown ๏ Carga del modelo ๏ Preparación del Persistent Store en memoria. 13viernes, 8 de marzo de 13
    • Preparación ★ - (void) setUp { [super setUp]; // Create the Core Data stack. NSBundle *bundle = [NSBundle bundleForClass:[self class]]; model = [NSManagedObjectModel mergedModelFromBundles:@[bundle]]; coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; store = [coordinator addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:coordinator]; // Instantiate three products for the tests. mainGroup = [NSEntityDescription insertNewObjectForEntityForName:@"Group" inManagedObjectContext:context]; mainGroup.name = @"A cool group"; } - (void) tearDown { context = nil; store = nil; coordinator = nil; model = nil; [super tearDown]; } 14viernes, 8 de marzo de 13
    • No duplicación ★ #pragma mark - Detect duplication - (void) testDuplicatedIsDetectedWhenTwoGroupsWithSameName { Group *anotherGroup = [NSEntityDescription insertNewObjectForEntityForName:@"Group" inManagedObjectContext:context]; anotherGroup.name = @"A cool group"; STAssertTrue([mainGroup isDuplicated], @"If another group exists with the same name it is considered a duplicate"); } - (void) testDuplicatedIsNotDetectedWhenTwoGroupsWithDifferentName { Store *anotherStore = [NSEntityDescription insertNewObjectForEntityForName:@"Group" inManagedObjectContext:context]; anotherGroup.name = @"Another cool group"; STAssertFalse([mainGroup isDuplicated], @"If no other group exists with a different name it is not considered a duplicate"); } 15viernes, 8 de marzo de 13
    • Desacoplamientoviernes, 8 de marzo de 13
    • This is an open discussionviernes, 8 de marzo de 13
    • Marco Arment “It’s so simple, I’ll just use plist”viernes, 8 de marzo de 13
    • El problema ★ Acoplamiento fuerte entre los componentes de la aplicación (Models, Views, Controllers) ★ Difícil hacer mocks para aplicar TDD ๏ TDD = componentes independientes que se testan de forma independiente entre sí ๏ (¿Mocking de NSManagedObjectContext?) 19viernes, 8 de marzo de 13
    • El problema ★ Para testear un View Controller que muestra en pantalla un listado de Meetings tendría que: ๏ Crear un NSManagedObjectContext ๏ Cargar datos de prueba en la base de datos ๏ Hacer una búsqueda sobre los datos de prueba ๏ Generar un NSFetchedResultsController ๏ ...y finalmente testear 20viernes, 8 de marzo de 13
    • El problema ★ ...y después de hacer todo esto, resulta que queremos hacer una versión para Android y compartir datos usando Parse/RoR/PHP/DynamoDB ★ ...o prefieres usar plists para no complicarte la vida (como Marco Arment, pero al revés) 21viernes, 8 de marzo de 13
    • El problema http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-yearsviernes, 8 de marzo de 13
    • “The database is a DETAIL of our application”viernes, 8 de marzo de 13
    • View Controller Model View (Core Data) Meeting.hviernes, 8 de marzo de 13
    • Como la base de datos es un detalle, la podemos quitarviernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controllerviernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller Entidad: Objeto que contiene las MeetingEntity reglas de negocio genéricas, aquellas que se pueden aplicar GroupEntity siempre en cualquier contexto. AttendeeEntity Por ejemplo: Si a un evento se inscriben tres personas, este queda automáticamente confirmadoviernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity Interactor: Contiene reglas de negocio específicas Por ejemplo: Dos eventos no pueden tener el mismo nombreviernes, 8 de marzo de 13
    • ¿Y la base de datos?viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller Request Object MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity Request Object AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity Request Object MeetingsGateway ...accede a Core Data y busca los objetos que cumplen los criterios del RequestObject Model (Core Data)viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity Entidades AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller MeetingEntity ResponseObject BrowseMeetingsInteractor GroupEntity Entidades AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller ResponseObject MeetingEntity View BrowseMeetingsInteractor GroupEntity AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
    • show me the code!!viernes, 8 de marzo de 13
    • Duck Typingviernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller RequestObjectProtocol ResponseObject MeetingEntity View BrowseMeetingsInteractor GroupEntity AttendeeEntity ResponseObject MeetingsParseGateway Model (Core Data)viernes, 8 de marzo de 13
    • QueryRequestProtocol @protocol QueryRequestProtocol @required @property (nonatomic, strong) NSString *queryString; @property (nonatomic, strong, readonly) NSDictionary *components; @endviernes, 8 de marzo de 13
    • BrowseMeetingsInteractor@interface BrowseMeetingsInteractor : NSObject@property (nonatomic, strong) id<MeetingGatewayProtocol> gateway;- (id<StandardResponseProtocol>) getResponseForRequest:(id<QueryRequestProtocol >)request;@endviernes, 8 de marzo de 13
    • MeetingGateway @class MeetingEntity; @protocol MeetingGatewayProtocol <NSObject> @required - (id)processRequest:(id<QueryRequestProtocol> *)request; - (void)save:(MeetingEntity *)meeting error:(NSError **)error; @endviernes, 8 de marzo de 13
    • viewDidLoad if (!self.fridgeResponse) { IFCoreDataFridgeGateway *gateway = [[IFCoreDataFridgeGateway alloc] init]; gateway.shouldReturnFetchResultsController = false; gateway.context = self.context; self.fridgeInteractor = [[IFViewFridgeInteractor alloc] init]; self.fridgeInteractor.gateway = gateway; self.fridgeResponse = [self.fridgeInteractor getResponseForRequest:self.request]; } if (!self.fridgeItemsResponse) { IFCoreDataFridgeItemGateway *fridgeItemGateway = [[IFCoreDataFridgeItemGateway alloc] init]; fridgeItemGateway.context = self.context; fridgeItemGateway.shouldReturnFetchResultsController = YES; self.fridgeItemsInteractor = [[IFBrowseFridgeItemsInteractor alloc] init]; self.fridgeItemsInteractor.gateway = fridgeItemGateway; IFFridgeItemRequestObject *fridgeItemsRequest = [[IFFridgeItemRequestObject alloc] init]; ; fridgeItemsRequest.queryString = [NSString stringWithFormat:@"fridge-slug= %@",self.request.components[@"slug"] ] ; self.fridgeItemsResponse = [self.fridgeItemsInteractor getResponseForRequest:fridgeItemsRequest]; }viernes, 8 de marzo de 13
    • ¿Cómo cambiamos de CoreData a Parse?viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller RequestObject ResponseObject MeetingEntity View BrowseMeetingsInteractor GroupEntity AttendeeEntity ResponseObject MeetingsParseGateway Mientras la clase MeetingsParseGateway y MeetingsGateway cumplan Cache el mismo protocolo ¡todo Parse (Core Data) funciona!viernes, 8 de marzo de 13
    • ¡¡Podemos testearlo todo!!viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller RequestObject ResponseObject MeetingEntity View BrowseMeetingsInteractor GroupEntity AttendeeEntity ResponseObject MeetingsParseGateway Cache Parse (Core Data)viernes, 8 de marzo de 13
    • View Controller RequestObject MockResponseObject MockBrowseMeetingsInteractor Para testear el view controller, basta con tener un Mock del Interactorviernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting View Controller RequestObject ResponseObject MeetingEntity View BrowseMeetingsInteractor GroupEntity AttendeeEntity ResponseObject MeetingsParseGateway Mientras la clase MeetingsParseGateway y MeetingsGateway cumplan Cache el mismo protocolo ¡todo Parse (Core Data) funciona!viernes, 8 de marzo de 13
    • Request: quiero ver todos los objetos Meeting MockRequestObject ResponseObject MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity MockGatewayviernes, 8 de marzo de 13
    • En la vida nada es gratis... ★ Hay que escribir más código ★ Hay que pensar en interfaces (protocolos) ★ ¿Rendimiento? 51viernes, 8 de marzo de 13
    • ¿Qué pensáis? 52viernes, 8 de marzo de 13
    • ¿Qué pensáis? ★ ¿Creéis que tener un sistema de componentes realmente desacoplados es un buen sistema? 52viernes, 8 de marzo de 13
    • ¿Qué pensáis? ★ ¿Creéis que tener un sistema de componentes realmente desacoplados es un buen sistema? ★ ¿Pensáis que tener un sistema en el que puedo testear sus componentes por separado, es un buen sistema? 52viernes, 8 de marzo de 13
    • ¿Qué pensáis? ★ ¿Creéis que tener un sistema de componentes realmente desacoplados es un buen sistema? ★ ¿Pensáis que tener un sistema en el que puedo testear sus componentes por separado, es un buen sistema? ★ ¿Pensáis que tener un sistema flexible en el que puedo posponer las decisiones sobre aspectos fundamentales del mismo hasta que realmente las necesito es un buen sistema? 52viernes, 8 de marzo de 13
    • ¿Qué pensáis? ★ ¿Creéis que tener un sistema de componentes realmente desacoplados es un buen sistema? ★ ¿Pensáis que tener un sistema en el que puedo testear sus componentes por separado, es un buen sistema? ★ ¿Pensáis que tener un sistema flexible en el que puedo posponer las decisiones sobre aspectos fundamentales del mismo hasta que realmente las necesito es un buen sistema? ★ ¿Pensáis que poder sustituir unas componentes por otras es un buen sistema? 52viernes, 8 de marzo de 13
    • ¿Qué pensáis? ★ ¿Creéis que tener un sistema de componentes realmente desacoplados es un buen sistema? ★ ¿Pensáis que tener un sistema en el que puedo testear sus componentes por separado, es un buen sistema? ★ ¿Pensáis que tener un sistema flexible en el que puedo posponer las decisiones sobre aspectos fundamentales del mismo hasta que realmente las necesito es un buen sistema? ★ ¿Pensáis que poder sustituir unas componentes por otras es un buen sistema? ★ ¿Pensáis que un sistema en el que una buena parte del código se puede autogenerar o reutilizar es un buen sistema? 52viernes, 8 de marzo de 13
    • Cuando no... ★ Si quieres hacer un prototipo rápido de la applicación ★ Si no quieres hacer TDD, ni desacoplar componentes ni dormir más tranquilo por las noches cuando tu app se la descarguen miles de personas 53viernes, 8 de marzo de 13
    • ¡Es una inversión de futuro! ¡Gracias!viernes, 8 de marzo de 13
    • Recursos recomendados ★ Architecture: the lost years ★ Test Driven iOS Development ★ www.cleancoders.com/ 55viernes, 8 de marzo de 13