Bienvenido a la republica independiente de las pruebas unitarias con Core Data
-
Upload
alfonso-alba -
Category
Technology
-
view
1.251 -
download
0
description
Transcript of Bienvenido a la republica independiente de las pruebas unitarias con Core Data
Bienvenido a la república independiente de las pruebas
unitarias con Core DataJorge 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
2
viernes, 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)
4
viernes, 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.
5
viernes, 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 ⇒
★ Soluciones:๏ Aprovechar el control de versiones
๏ mogenerator (http://rentzsch.github.com/mogenerator/) de W. Rentzsch
๏ Categorías
7
métodos añadidos
viernes, 8 de marzo de 13
Implementación del modelo de negocio★ Modificar modelo de datos ⇒
regenerar clases. Xcode sobreescribe ⇒
★ Soluciones:๏ Aprovechar el control de versiones
๏ mogenerator (http://rentzsch.github.com/mogenerator/) de W. Rentzsch
๏ Categorías
7
métodos añadidos
viernes, 8 de marzo de 13
Modelo NSCoderApp
viernes, 8 de marzo de 13
Group (Generado)★ #import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class Person;
@interface Group : NSManagedObject
@property (nonatomic, retain) NSString * name;@property (nonatomic, retain) NSString * notes;@property (nonatomic, retain) NSSet *members;@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;
@end
#import "Group.h"#import "Person.h"
@implementation Group
@dynamic name;@dynamic notes;@dynamic members;@dynamic meetings;
@end
viernes, 8 de marzo de 13
Categoría Group+Model★ #import "Group.h"
@interface Group (Model)
- (BOOL) isDuplicated;
@end
#import "Group+Model.h"
@implementation Group (Model)
#pragma mark - Detect duplicates
/** Verify that this item doesn't exist yet (another section with the same name). */- (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;}
@end
viernes, 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
12
viernes, 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.
13
viernes, 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];}
14
viernes, 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");}
15
viernes, 8 de marzo de 13
Desacoplamientoviernes, 8 de marzo de 13
This is an open discussion
viernes, 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?)
19
viernes, 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
20
viernes, 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)
21
viernes, 8 de marzo de 13
El problema
http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-years
viernes, 8 de marzo de 13
“The database is a DETAIL of our application”
viernes, 8 de marzo de 13
View Controller
Model(Core Data)
View
Meeting.h
viernes, 8 de marzo de 13
Como la base de datos es un detalle, la podemos quitar
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
Entidad: Objeto que contiene las reglas de negocio genéricas, aquellas que se pueden aplicar siempre en cualquier contexto.
Por ejemplo: Si a un evento se inscriben tres personas, este queda automáticamente confirmado
MeetingEntity
GroupEntity
AttendeeEntity
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
Interactor: Contiene reglas de negocio específicas
Por ejemplo: Dos eventos no pueden tener el mismo nombre
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
viernes, 8 de marzo de 13
¿Y la base de datos?
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
Request Object
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
Request Object
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
Request Object
...accede a Core Data y busca los objetos que cumplen los criterios del RequestObject
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
Entidades
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
Entidades
ResponseObject
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsGateway
Model(Core Data)
ResponseObject
View
viernes, 8 de marzo de 13
show me the code!!
viernes, 8 de marzo de 13
Duck Typing
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsParseGateway
Model(Core Data)
ResponseObject
View
RequestObjectProtocol
ResponseObject
viernes, 8 de marzo de 13
QueryRequestProtocol
@protocol QueryRequestProtocol
@required@property (nonatomic, strong) NSString *queryString;@property (nonatomic, strong, readonly) NSDictionary *components;
@end
viernes, 8 de marzo de 13
BrowseMeetingsInteractor
@interface BrowseMeetingsInteractor : NSObject
@property (nonatomic, strong) id<MeetingGatewayProtocol> gateway;
- (id<StandardResponseProtocol>) getResponseForRequest:(id<QueryRequestProtocol >)request;@end
viernes, 8 de marzo de 13
MeetingGateway
@class MeetingEntity;
@protocol MeetingGatewayProtocol <NSObject>
@required- (id)processRequest:(id<QueryRequestProtocol> *)request;- (void)save:(MeetingEntity *)meeting error:(NSError **)error;@end
viernes, 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
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsParseGateway
Cache(Core Data)
ResponseObject
View
Parse
Mientras la clase MeetingsParseGateway y
MeetingsGateway cumplan el mismo protocolo ¡todo
funciona!
RequestObject
ResponseObject
viernes, 8 de marzo de 13
¡¡Podemos testearlo todo!!
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsParseGateway
Cache(Core Data)
ResponseObject
View
Parse
RequestObject
ResponseObject
viernes, 8 de marzo de 13
View Controller
MockBrowseMeetingsInteractor
MockResponseObject
RequestObject
Para testear el view controller, basta con tener un Mock del
Interactor
viernes, 8 de marzo de 13
View Controller
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MeetingsParseGateway
Cache(Core Data)
ResponseObject
View
Parse
Mientras la clase MeetingsParseGateway y
MeetingsGateway cumplan el mismo protocolo ¡todo
funciona!
RequestObject
ResponseObject
viernes, 8 de marzo de 13
Request:quiero ver todos los
objetos Meeting
MeetingEntity
GroupEntity
AttendeeEntity
BrowseMeetingsInteractor
MockGateway
MockRequestObjectResponseObject
viernes, 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?
51
viernes, 8 de marzo de 13
¿Qué pensáis?
52
viernes, 8 de marzo de 13
¿Qué pensáis?★ ¿Creéis que tener un sistema de componentes
realmente desacoplados es un buen sistema?
52
viernes, 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?
52
viernes, 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?
52
viernes, 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?
52
viernes, 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?
52
viernes, 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
53
viernes, 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/
55
viernes, 8 de marzo de 13