2009-07-06 7 views
6

Je me demande comment procéder pour tester cela. J'ai une méthode qui prend un paramètre, et basé sur certaines propriétés de ce paramètre, il crée un autre objet et fonctionne sur elle. Le code ressemble à ceci:Comment puis-je tester un objet interne à une méthode en Objective-C?

- (void) navigate:(NavContext *)context { 
    Destination * dest = [[Destination alloc] initWithContext:context]; 
    if (context.isValid) { 
    [dest doSomething]; 
    } else { 
    // something else 
    } 
    [dest release]; 
} 

Ce que je veux vérifier est que si context.isValid est vrai, que doSomething est appelé dest, mais je ne sais pas comment tester (ou si c'est même possible) en utilisant OCMock ou toute autre méthode de test traditionnelle puisque cet objet est entièrement créé dans le cadre de la méthode. Est-ce que je vais à ce sujet dans le mauvais sens?

Répondre

5

Vous pourriez utilisez OCMock, mais vous devrez modifier le code pour prendre un objet Destination ou pour utiliser un objet singleton que vous pouvez remplacer par votre objet fantaisie en premier.

La propre façon de le faire serait probablement de mettre en œuvre une méthode

-(void) navigate:(NavContext *)context destination:(Destination *)dest; 

. Changer la mise en œuvre de -(void) navigate:(NavContext *)context à ce qui suit:

- (void) navigate:(NavContext *)context { 
    Destination * dest = [[Destination alloc] initWithContext:context]; 
    [self navigate:context destination:dest]; 
    [dest release]; 
} 

Cela permettra à vos tests pour appeler la méthode avec un paramètre supplémentaire directement. (Dans d'autres langages, vous implémenteriez ceci simplement en fournissant une valeur par défaut pour le paramètre de destination, mais Objective-C ne supporte pas les paramètres par défaut.)

+0

Cela semble être la réponse la plus facile, mais il semble un peu compliqué d'avoir l'objet de destination dans plusieurs portées quand ce n'est pas vraiment nécessaire ailleurs, juste pour supporter des tests faciles. Je suppose que les compromis doivent être faits quelque part :) – Kevlar

+0

Yep; Si vous voulez être capable de substituer un objet Destination séparé, alors vous devez avoir un moyen de le transmettre. C'est le moyen le plus propre que je connaisse de le faire. –

0

Il est tout à fait possible, en utilisant des techniques aussi intéressantes que method swizzling, mais cela ne va probablement pas dans le bon sens. S'il n'y a absolument aucun moyen d'observer les effets de l'appel de doSomething à partir d'un test unitaire, n'est-ce pas le fait qu'il appelle doSomething un détail d'implémentation?

(Si vous deviez faire ce test, une façon d'atteindre vos objectifs remplacerions la méthode doSomething de Destination avec qui avertit votre test unitaire et passe ensuite l'appel à doSomething.)

+0

Je suppose que ce n'est pas une grosse affaire si je ne teste pas que la méthode est appelée. Je suis encore un peu nouveau dans le test TDD/unité donc je ne connais pas encore toutes les meilleures pratiques :) – Kevlar

1

Ce que je veux vérifier est que si context.isValid est vrai , that doQuelqu'un est appelé à dest

Je pense que vous pouvez tester la mauvaise chose ici. Vous pouvez supposer (j'espère) que les instructions booléennes fonctionnent correctement dans ObjC. Ne voudriez-vous pas tester l'objet Context à la place? Si context.isValid vous garantit que la branche [dest doSomething] est exécutée.

+0

Je me moque de l'objet de contexte puisque dans ce test je me fous de la façon dont il est créé, je m'inquiète que la méthode fasse la bonne chose en fonction des propriétés du contexte. – Kevlar

+0

En outre, si l'implémentation de navigate: ever change, il peut toujours vouloir vérifier que doSomething est appelé. –

+0

Ahh assez juste, j'étais un peu myope. J'aime l'approche de BJ Homer alors. – EightyEight

0

J'aime utiliser les méthodes d'usine dans cette situation.

@interface Destination(Factory) 
+ (Destination *)destinationWithContext:(NavContext *)context; 
@end 

@implementation Destination(Factory) 
+ (Destination *)destinationWithContext:(NavContext *)context 
{ 
    return [[Destination alloc] initWithContext:context]; 
} 
@end 

Je fais alors un FakeClass:

#import "Destination+Factory.h" 

@interface FakeDestination : Destination 
+ (id)sharedInstance; 
+ (void)setSharedInstance:(id)sharedInstance; 
// Note! Instance method! 
- (Destination *)destinationWithContext:(NavContext *)context; 
@end 

@implementation FakeDestination 
+ (id)sharedInstance 
{ 
    static id _sharedInstance = nil; 
    if (!_sharedInstance) 
    { 
     _sharedInstance = [[FakeDestination alloc] init]; 
    } 
    return _sharedInstance; 
} 
+ (void)setSharedInstance:(id)sharedInstance 
{ 
    _sharedInstance = sharedInstance; 
} 
// Overrides 
+ (Destination *)destinationWithContext:(NavContext *)context { [FakeDestination.sharedInstance destinationWithContext:context]; } 
// Instance 
- (Destination *)destinationWithContext:(NavContext *)context { return nil; } 
@end 

Une fois que vous le mettre en place, il vous suffit de swizzle the class methods pour + (Destination *)destinationWithContext:(NavContext *)context;

Maintenant, vous êtes prêt à:

id destinationMock = [OCMock mockForClass:FakeDestination.class]; 
// do the swizzle 
[FakeDestination setSharedInstance:destinationMock]; 
[[destinationMock expect] doSomething]; 
// Call your method 
[destinationMock verify]; 

C'est une bonne quantité de codage à l'avant, mais c'est très réutilisable.

Questions connexes