cocoaheads rennes #10 : mock objects
DESCRIPTION
Slides de la session des CocoaHeads Rennaise du 10 mai 2012. Session présentée par Quentin Arnault.TRANSCRIPT
Mock objects, théorie et application
Quentin ArnaultCocoaHeads Rennes #1010 mai 2012
Ce que vous allez apprendre
➡ ce que sont les mock objects,
➡ ce qu’ils vous apporteront,
➡ les utiliser grâce à OCMock.
Contexte et regard sur la POO
Photo d’identité et
photo de famille
Montre moi du code !
Retour d’expérience
Contexte et regard sur la POO
Contexte ?
tests automatisés
outils
plus facile
plus efficace
meilleure maintenabilité
3 niveaux de tests automatisés
• unitaire,
• intégration,
• de bout en bout. unitaire
intégration
bout en boutqualité du feeback
Steve Freeman, Nat Pryce. Growing Object-oriented software, Guided by tests, p11
qualité externe qualité interne
Back to basics (POO)
➡ Responsabilité : obligation d’accomplir une tâche ou de détenir une information
➡ Rôle : ensemble de responsabilités liées entre elles
➡ Objet : implémentation d’un ou plusieurs rôles
➡ Collaboration : interactions d’ objets ou de rôles
C’est à dire
Responsabilités
➡ construire les requêtes HTTP
➡ traiter les erreurs HTTP/svc
➡ informer du résultat
Collaborateurs
➡ client HTTP
➡ parser
➡ observateur
Webservice Client
Webservice ClientWebservice Client Delegate
HTTP Client
Parser
W. Cl.
HTTP Cl.
parser
observ.
...
...
...
...... ...
...
...
...
... ...
...
...
...
... ...
W. Cl.
HTTP Cl.
parser
observ.
...
...
...
...... ...
...
...
...
... ...
...
...
... ...
test object
test object
test object
Photo d’identité et photo de famille
objets factices (dummy object)
➡ vient remplacer «bêtement» l’objet de production
➡ mais pas de comportements
objets bouchon (stub object)
➡ vient remplacer un objet de production
➡ retourne des valeurs
➡ comportement souvent partiel
objets allégés (fake object)
➡ vient remplacer un objet de production
➡ contient une implémentation fonctionnelle mais non adaptée à la production
mocks objects (mock object)
➡ vient remplacer un objet de production
➡ peut retourner des valeurs (comme les objets bouchons)
➡ contrôle les messages reçus par rapport au contrat prévu
➡ Première phase : enregistrement du contrat
➡ Deuxième phase : enregistrement des messages pendant l’exécution du cas de test
Montre moi du code !
Une installation simple1
2
3
Premiers pas
WebServiceClient *client = [[WebServiceClient alloc] initWithHTTPClient:httpClient];
HTTPClient *httpClient = [[HTTPClient alloc] init];
WebServiceClient *client = [[WebServiceClient alloc] initWithHTTPClient:mockClient];
id mockClient = [OCMockObject mockForClass:[HTTPClient class]];
Premiers pas
[[mockClient expect] prepare];
[mockClient verify];
[client fetchUsers];
Enregistrement du contrat
Enregistrement des messages
expect[[mock expect] aMethod];
[[mock expect] aMethodWithParameter:anObject];
[[mock expect] aMethodWithParameter:[OCMArg any]];
[[mock expect] aMethodWithParameter:[OCMArg anyPointer]];
[[mock expect] aMethodWithParameter:[OCMArg isNotEqual:aValue]];
[[mock expect] aMethodWithParameter:[OCMArg checkWithSelector:@selector() onObject:anObject]];
[[mock expect] aMethodWithParameter:[OCMArg checkWithBlock:^BOOL(id value){}]];
verify[mock verify];
Test Suite 'Tests' started.Starting TestIsLife/test_missing_call2012-05-10 14:47:49.413 runTests[10490:13303] ! Name: NSInternalInconsistencyException! File: Unknown! Line: Unknown! Reason: OCMockObject[HTTPClient]: expected method was not invoked: prepare
#0 0x1b9a022 __exceptionPreprocess() (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.1.sdk/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation)#1 0x1f76cd6 objc_exception_throw() (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.1.sdk/usr/lib/libobjc.A.dylib)#2 0x1b42a48 +[NSException raise:format:arguments:] (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.1.sdk/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation)#3 0x1b429b9 +[NSException raise:format:] (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/...Blah blah blah...
stub
[[mock stub] aMethod];
[[[mock stub] andReturn:aValue] aMethod];
[[[mock stub] andThrow:anException] aMethod];
[[[mock stub] andPost:aNotification] aMethod];
[[[mock stub] andCall:@selector() onObject:anObject] aMethod];
[[[mock stub] andDo:^(NSInvocation *){}] aMethod];
stub[[[mock stub] andReturn:aValue] aMethod];
[[[mock stub] andPost:aNotification] aMethod];
[[[[mock stub] andReturn:aValue] andPost:aNotification] aMethod];
[[[mock stub] andReturn:aValue] aMethodWithParameter:[OCMArg isNotNil]];
[[[mock stub] andThrow:anException] aMethodWithParameter:[OCMArg isNil]];
➡ permet de préciser les appels de méthodes que l’on attend
➡ ne supporte aucun appel en dehors de ceux définis
id mockClient = [OCMockObject mockForClass:[HTTPClient class]];
[[mockClient expect] prepare];[[[mockClient stub] andReturn:@"tournament"] getBaseUrl];[[[mockClient stub] andReturn:@"HTTPS"] getProtocol];[[[mockClient stub] setTimeout:10];
➡ permet de préciser les appels de méthodes que l’on attend
➡ ignore tous les appels de méthodes non prévus
id mockClient = [OCMockObject niceMockForClass:[HTTPClient class]];
[[mock expect] prepare]
[[mock reject] badMethod]
➡ permet de préciser les appels de méthodes que l’on attend
➡ tout en conservant le comportement de l’objet remplacé
id mockClient = [OCMockObject partialMockForObject:httpClient];
[[[mock expect] andForwardToRealObject] prepare]
➡ permet d’observer des notifications
id mockObserver = [OCMockObject observerMock];
[notificationCenter addMockObserver:mockObserver name:notificationName object:nil];[[mockObserver expect] notificationWithName:notificationName object:[OCMArg any]]
Retour d’expérience
- (void)test_should_initilize_http_client { // arrange id mockClient = [OCMockObject mockForClass:[HTTPClient class]]; [[mockClient stub] setTimeout:[OCMArg any]]; [[mockClient expect] prepare]; WebServiceClient *client = [[WebServiceClient alloc] initWithHTTPClient:mockClient];
// act [client fetchUsers];
// assert [mockClient verify];}
- (void)test_should_initilize_http_client { // arrange [[self.mockClient expect] prepare]; WebServiceClient *client = [[WebServiceClient alloc] initWithHTTPClient:self.mockClient];
// act [client fetchUsers];
// verify [self.mockClient verify];}
@property (nonatomic, readonly) id mockClient;
@synthesize mockClient = mockClient_;
- (id)mockClient { if (!mockClient_) mockClient_ = [ OCMockObject mockForClass:[HTTPClient class]]; [[mockClient_ stub] setTimeout:[OCMArg any]]; } return mockClient_;}
- (void)test_should_initilize_http_client { // arrange [[self.mockClient expect] prepare]; WebServiceClient *client = [[WebServiceClient alloc] initWithHTTPClient:self.mockClient];
// act [client fetchUsers];
// assert [self.mockClient verify];}
- (void)setUp { [super setUp];
mockClient_ = nil;}
- (void)tearDown { [super tearDown];
[self.mockClient verify];}
- (void)test_should_initilize_http_client { // arrange [[self.mockClient expect] prepare]; WebServiceClient *client = [[WebServiceClient alloc] initWithHTTPClient:self.mockClient];
// act [client fetchUsers];}
Pourquoi les utiliser ?
➡ minimisation des interactions
➡ minimisation de l’exposition de l’état de l’objet testé
➡ tests plus rapide
Ce qu’il m’ont apporté ?
➡ différencier les tests d’état des tests de collaboration
➡ nouvel angle d’analyse d’un design : l’interaction VS la classification
➡ l’importance d’avoir des dépendances explicites
Références➡ http://www.cocoaheads.fr
➡ Steve Freeman, Nat Pryce. Growing Object-oriented software, Guided by tests
➡ http:// www.mockobjects.com
➡ http://ocmock.org
➡ http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-at-lrug/
Écrivez les tests que vous voudriez lire.
Mail : [email protected]
Mock objects, théorie et application