CocoaHeads Rennes #10 : Mock Objects

Preview:

DESCRIPTION

Slides de la session des CocoaHeads Rennaise du 10 mai 2012. Session présentée par Quentin Arnault.

Citation preview

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

Écrivez les tests que vous voudriez lire.

julien@cocoaheads.fr

thomas.dupont@cocoaheads.frCocoaHeads #10

Mail : quentin.arnault@gmail.com

Mock objects, théorie et application