Your SlideShare is downloading. ×
Bienvenido a la republica independiente de las pruebas unitarias con Core Data
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

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

1,271

Published on

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.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,271
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
5
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. 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
  • 2. Agenda ★ MVC ★ Implementación en Core Data ★ Pruebas Unitarias ★ Desacoplamiento ★ Conclusiones 2viernes, 8 de marzo de 13
  • 3. El modelo a seguirviernes, 8 de marzo de 13
  • 4. 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
  • 5. 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
  • 6. Usa Core Data, Lukeviernes, 8 de marzo de 13
  • 7. 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
  • 8. 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
  • 9. Modelo NSCoderAppviernes, 8 de marzo de 13
  • 10. 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
  • 11. 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
  • 12. Pruebasviernes, 8 de marzo de 13
  • 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
  • 14. 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
  • 15. 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
  • 16. 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
  • 17. Desacoplamientoviernes, 8 de marzo de 13
  • 18. This is an open discussionviernes, 8 de marzo de 13
  • 19. Marco Arment “It’s so simple, I’ll just use plist”viernes, 8 de marzo de 13
  • 20. 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
  • 21. 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
  • 22. 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
  • 23. El problema http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-yearsviernes, 8 de marzo de 13
  • 24. “The database is a DETAIL of our application”viernes, 8 de marzo de 13
  • 25. View Controller Model View (Core Data) Meeting.hviernes, 8 de marzo de 13
  • 26. Como la base de datos es un detalle, la podemos quitarviernes, 8 de marzo de 13
  • 27. Request: quiero ver todos los objetos Meeting View Controllerviernes, 8 de marzo de 13
  • 28. 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
  • 29. 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
  • 30. ¿Y la base de datos?viernes, 8 de marzo de 13
  • 31. Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
  • 32. 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
  • 33. 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
  • 34. 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
  • 35. Request: quiero ver todos los objetos Meeting View Controller MeetingEntity BrowseMeetingsInteractor GroupEntity Entidades AttendeeEntity MeetingsGateway Model (Core Data)viernes, 8 de marzo de 13
  • 36. 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
  • 37. 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
  • 38. show me the code!!viernes, 8 de marzo de 13
  • 39. Duck Typingviernes, 8 de marzo de 13
  • 40. 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
  • 41. QueryRequestProtocol @protocol QueryRequestProtocol @required @property (nonatomic, strong) NSString *queryString; @property (nonatomic, strong, readonly) NSDictionary *components; @endviernes, 8 de marzo de 13
  • 42. BrowseMeetingsInteractor@interface BrowseMeetingsInteractor : NSObject@property (nonatomic, strong) id<MeetingGatewayProtocol> gateway;- (id<StandardResponseProtocol>) getResponseForRequest:(id<QueryRequestProtocol >)request;@endviernes, 8 de marzo de 13
  • 43. 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
  • 44. 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
  • 45. ¿Cómo cambiamos de CoreData a Parse?viernes, 8 de marzo de 13
  • 46. 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
  • 47. ¡¡Podemos testearlo todo!!viernes, 8 de marzo de 13
  • 48. 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
  • 49. View Controller RequestObject MockResponseObject MockBrowseMeetingsInteractor Para testear el view controller, basta con tener un Mock del Interactorviernes, 8 de marzo de 13
  • 50. 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
  • 51. Request: quiero ver todos los objetos Meeting MockRequestObject ResponseObject MeetingEntity BrowseMeetingsInteractor GroupEntity AttendeeEntity MockGatewayviernes, 8 de marzo de 13
  • 52. 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
  • 53. ¿Qué pensáis? 52viernes, 8 de marzo de 13
  • 54. ¿Qué pensáis? ★ ¿Creéis que tener un sistema de componentes realmente desacoplados es un buen sistema? 52viernes, 8 de marzo de 13
  • 55. ¿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
  • 56. ¿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
  • 57. ¿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
  • 58. ¿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
  • 59. 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
  • 60. ¡Es una inversión de futuro! ¡Gracias!viernes, 8 de marzo de 13
  • 61. Recursos recomendados ★ Architecture: the lost years ★ Test Driven iOS Development ★ www.cleancoders.com/ 55viernes, 8 de marzo de 13

×