Este documento presenta una introducción a las pruebas unitarias con Core Data. Explica los conceptos de MVC, la implementación de Core Data, y cómo realizar pruebas unitarias de forma efectiva. También discute cómo desacoplar los componentes para facilitar las pruebas y la flexibilidad al cambiar la capa de datos subyacente.
Bienvenido a la republica independiente de las pruebas unitarias con Core Data
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
2
viernes, 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)
4
viernes, 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.
5
viernes, 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
7
viernes, 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
7
viernes, 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 doesn't 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;
}
@end
viernes, 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
12
viernes, 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.
13
viernes, 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];
}
14
viernes, 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");
}
15
viernes, 8 de marzo de 13
18. This is an
open discussion
viernes, 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?)
19
viernes, 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
20
viernes, 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)
21
viernes, 8 de marzo de 13
23. El problema
http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-years
viernes, 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.h
viernes, 8 de marzo de 13
26. Como la base de datos
es un detalle, la
podemos quitar
viernes, 8 de marzo de 13
27. Request:
quiero ver todos los
objetos Meeting
View Controller
viernes, 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 confirmado
viernes, 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 nombre
viernes, 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
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;
@end
viernes, 8 de marzo de 13
42. BrowseMeetingsInteractor
@interface BrowseMeetingsInteractor : NSObject
@property (nonatomic, strong) id<MeetingGatewayProtocol> gateway;
- (id<StandardResponseProtocol>) getResponseForRequest:
(id<QueryRequestProtocol >)request;
@end
viernes, 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;
@end
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
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
Interactor
viernes, 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
MockGateway
viernes, 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?
51
viernes, 8 de marzo de 13
54. ¿Qué pensáis?
★ ¿Creéis que tener un sistema de componentes
realmente desacoplados es un buen sistema?
52
viernes, 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?
52
viernes, 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?
52
viernes, 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?
52
viernes, 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?
52
viernes, 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
53
viernes, 8 de marzo de 13